diff --git a/include/tercontrol.h b/include/tercontrol.h new file mode 100644 index 0000000..7b6d298 --- /dev/null +++ b/include/tercontrol.h @@ -0,0 +1,443 @@ +/********************************************************************************** + + Basic Terminal Control Library + Copyright 2022 Zackery Smith + This library is released under the GPLv3 license + + This library has no dependencies other than the standard C runtime library + +***********************************************************************************/ +#ifndef TC_H +#define TC_H +#endif + +#include +#ifdef _WIN32 +#include +#include // For _getch() function +HANDLE hConsole = INVALID_HANDLE_VALUE, hAlternateScreen = INVALID_HANDLE_VALUE; // WinAPI structures for console +CONSOLE_SCREEN_BUFFER_INFO csbi; +CONSOLE_CURSOR_INFO cci; +#else + +#endif +#include +#include +#include +#include + +#ifdef _WIN32 +#define TC_NRM "" +#else +#define TC_NRM "\x1B[0m" /* Normalize color */ +#endif + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + +// `asprintf` is usable on any POSIX-2008 compliant system (any modern Linux system) +// My compiler likes to complain about it.. Another way to preform this is with `vfprintf` +// Or maybe I'm just a bad programmer :I + +#ifdef _WIN32 + +char *tc_color_id(uint8_t cid, int l) +{ // "l" flag is ignored, just to make it compatible with the POSIX version + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hConsole, (cid / 16) << 4 | (cid % 16)); + return ""; +} + +#else + +char *tc_color_id(uint8_t cid, int l) +{ + + char *esc; + if (!l) + { + asprintf(&esc, "\x1B[48;5;%dm", cid); + } + else + { + asprintf(&esc, "\x1B[38;5;%dm", cid); + } + return esc; +} + +////////////////////////////////////////////////////////////////// +// WARNING: WinAPI doesn't natively support 24-bit colors // +// So this function is not available on Windows systems // +////////////////////////////////////////////////////////////////// +char *tc_rgb(int r, int g, int b, int l) +{ + char *esc; + if (!l) + { + asprintf(&esc, "\x1B[48;2;%d;%d;%dm", r, g, b); + } + else + { + asprintf(&esc, "\x1B[38;2;%d;%d;%dm", r, g, b); + } + return esc; +} +#endif + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +////////////////////////////////////// + +void tc_get_cols_rows(int *cols, int *rows); + +////////////////////////////// +// Common private modes // +////////////////////////////// + +#ifdef _WIN32 + +void tc_hide_cursor() +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + cci.bVisible = FALSE; + SetConsoleCursorInfo(hConsole, &cci); +} +void tc_show_cursor() +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + cci.bVisible = TRUE; + SetConsoleCursorInfo(hConsole, &cci); +} + +void tc_enter_alt_screen() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + hAlternateScreen = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); + SetConsoleActiveScreenBuffer(hAlternateScreen); + SetStdHandle(STD_OUTPUT_HANDLE, hAlternateScreen); +} + +void tc_exit_alt_screen() +{ + if (hConsole == INVALID_HANDLE_VALUE) + { + return; + } + SetConsoleActiveScreenBuffer(hConsole); + CloseHandle(hAlternateScreen); + SetStdHandle(STD_OUTPUT_HANDLE, hConsole); +} + +#else + +#define tc_hide_cursor() puts("\033[?25l") +#define tc_show_cursor() puts("\033[?25h") + +/* These functions don't seem to be doing anything + + #define tc_save_screen() puts("\033[?47h") + #define tc_restore_screen() puts("\033[?47l") + +*/ +#define tc_enter_alt_screen() puts("\033[?1049h\033[H") +#define tc_exit_alt_screen() puts("\033[?1049l") +#endif +////////////////////////////// + +void tc_echo_off(); +void tc_echo_on(); + +void tc_get_cols_rows(int *cols, int *rows) +{ +#ifdef _WIN32 + + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +#else + struct winsize size; + ioctl(1, TIOCGWINSZ, &size); + *cols = size.ws_col; + *rows = size.ws_row; +#endif +} + +#ifdef _WIN32 +void tc_echo_off() // Not intended for user use +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode & (~ENABLE_ECHO_INPUT)); +} +void tc_echo_on() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode | ENABLE_ECHO_INPUT); +} + +void tc_canon_on() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode | ENABLE_ECHO_INPUT); +} + +void tc_canon_off() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode & (~ENABLE_ECHO_INPUT)); +} + +void tc_clear_partial(int x, int y, int width, int height) // Clears a section of the screen +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD count; + COORD homeCoords = {x, y}; + + if (hConsole == INVALID_HANDLE_VALUE) + { + return; + } + + if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) + { + return; + } + for (int i = 0; i < height; i++) + { + FillConsoleOutputCharacter(hConsole, ' ', width, homeCoords, &count); + FillConsoleOutputAttribute(hConsole, csbi.wAttributes, width, homeCoords, &count); + + homeCoords.Y++; + } + homeCoords.Y = y; + SetConsoleCursorPosition(hConsole, homeCoords); + return; +} + +void tc_get_cursor(int *x, int *y) +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + *x = csbi.dwCursorPosition.X; + *y = csbi.dwCursorPosition.Y; +} +void tc_set_cursor(int x, int y) +{ + COORD pos = {x, y}; + SetConsoleCursorPosition(hConsole, pos); +} + +void tc_move_cursor(int x, int y) +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + int cur_x = csbi.dwCursorPosition.X + x; + int cur_y = csbi.dwCursorPosition.Y + y; + if (cur_x < 0) + { + cur_x = 0; + } + if (cur_y < 0) + { + cur_y = 0; + } + COORD pos = {cur_x, cur_y}; + SetConsoleCursorPosition(hConsole, pos); + +} + +void tc_clear_screen() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + DWORD written; + DWORD bufSize = csbi.dwSize.X * csbi.dwSize.Y; + COORD homeCoords = {0, 0}; // Home coordinates + FillConsoleOutputCharacter(hConsole, ' ', bufSize, homeCoords, &written); +} + +void tc_clear_entire_line() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, csbi.dwCursorPosition.Y, csbi.dwSize.X, 1); +} + +void tc_clear_line_till_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, csbi.dwCursorPosition.Y, csbi.dwCursorPosition.X, 1); +} + +void tc_clear_line_from_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, csbi.dwSize.X, 1); +} + +void tc_clear_from_top_to_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, 0, csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y); +} + +void tc_clear_from_cursor_to_bottom() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, csbi.dwSize.X, csbi.dwSize.Y - csbi.dwCursorPosition.Y); +} + +void tc_print(const char *s) +{ + WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), s, strlen(s), NULL, NULL); +} + +int tc_getch() +{ + int ch = _getch(); + if(ch == 0 || ch == 0xE0) + { + ch += 255; + } + return ch; +} + +#else + +#define tc_clear_entire_line() puts("\x1B[2K") +#define tc_clear_line_till_cursor() puts("\x1B[1K") +#define tc_clear_line_from_cursor() puts("\x1B[0K") + +void tc_echo_off() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ECHO; + tcsetattr(1, TCSANOW, &term); +} + +void tc_echo_on() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ECHO; + tcsetattr(1, TCSANOW, &term); +} + +void tc_canon_on() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ICANON; + tcsetattr(1, TCSANOW, &term); +} + +void tc_canon_off() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ICANON; + tcsetattr(1, TCSANOW, &term); +} + +void tc_get_cursor(int *X, int *Y) +{ + tc_echo_off(); + tc_canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +#define tc_set_cursor(X, Y) printf("\033[%d;%dH", Y, X) +void tc_move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define tc_clear_screen() puts("\x1B[2J") +#define tc_clear_from_top_to_cursor() puts("\x1B[1J") +#define tc_clear_from_cursor_to_bottom() puts("\x1B[0J") + +void tc_clear_partial(int x, int y, int width, int height) +{ + char *buf = (char *)calloc(width + 1, 1); + memset(buf, 32, width); + tc_set_cursor(x, y); + for (int i = 0; i < height; i++) + { + tc_set_cursor(x, y + i); + fwrite(buf, width, 1, stdout); + } + free(buf); +} + +void tc_print(const char *s) +{ + fprintf(stdout, "%s", s); +} + +int tc_getch() // TODO: Implement this +{ + return 0; +} + +#endif diff --git a/include/utf8/LICENSE b/include/utf8/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/include/utf8/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/include/utf8/README.md b/include/utf8/README.md new file mode 100644 index 0000000..fc330f6 --- /dev/null +++ b/include/utf8/README.md @@ -0,0 +1,326 @@ +# 📚 utf8.h + +[![Actions Status](https://github.com/sheredom/utf8.h/workflows/CMake/badge.svg)](https://github.com/sheredom/utf8.h/actions) +[![Build status](https://ci.appveyor.com/api/projects/status/phfjjahhs9j4gxvs?svg=true)](https://ci.appveyor.com/project/sheredom/utf8-h) +[![Sponsor](https://img.shields.io/badge/💜-sponsor-blueviolet)](https://github.com/sponsors/sheredom) + +A simple one header solution to supporting utf8 strings in C and C++. + +Functions provided from the C header string.h but with a utf8* prefix instead of the str* prefix: + +[API function docs](#api-function-docs) + +string.h | utf8.h | complete | C++14 constexpr +---------|--------|---------|--------- +strcat | utf8cat | ✔ | +strchr | utf8chr | ✔ | ✔ +strcmp | utf8cmp | ✔ | ✔ +strcoll | utf8coll | | +strcpy | utf8cpy | ✔ | +strcspn | utf8cspn | ✔ | ✔ +strdup | utf8dup | ✔ | +strfry | utf8fry | | +strlen | utf8len | ✔ | ✔ +strnlen | utf8nlen | ✔ | ✔ +strncat | utf8ncat | ✔ | +strncmp | utf8ncmp | ✔ | ✔ +strncpy | utf8ncpy | ✔ | +strndup | utf8ndup | ✔ | +strpbrk | utf8pbrk | ✔ | ✔ +strrchr | utf8rchr | ✔ | ✔ +strsep | utf8sep | | +strspn | utf8spn | ✔ | ✔ +strstr | utf8str | ✔ | ✔ +strtok | utf8tok | | +strxfrm | utf8xfrm | | + +Functions provided from the C header strings.h but with a utf8* prefix instead of the str* prefix: + +strings.h | utf8.h | complete | C++14 constexpr +----------|--------|---------|--------- +strcasecmp | utf8casecmp | ~~✔~~ | ✔ +strncasecmp | utf8ncasecmp | ~~✔~~ | ✔ +strcasestr | utf8casestr | ~~✔~~ | ✔ + +Functions provided that are unique to utf8.h: + +utf8.h | complete | C++14 constexpr +-------|---------|--------- +utf8codepoint | ✔ | ✔ +utf8rcodepoint | ✔ | ✔ +utf8size | ✔ | ✔ +utf8size\_lazy | ✔ | ✔ +utf8nsize\_lazy | ✔ | ✔ +utf8valid | ✔ | ✔ +utf8nvalid | ✔ | ✔ +utf8makevalid | ✔ | +utf8codepointsize | ✔ | ✔ +utf8catcodepoint | ✔ | +utf8isupper | ~~✔~~ | ✔ +utf8islower | ~~✔~~ | ✔ +utf8lwr | ~~✔~~ | +utf8upr | ~~✔~~ | +utf8lwrcodepoint | ~~✔~~ | ✔ +utf8uprcodepoint | ~~✔~~ | ✔ + +## Usage ## + +Just `#include "utf8.h"` in your code! + +The current supported platforms are Linux, macOS and Windows. + +The current supported compilers are gcc, clang, MSVC's cl.exe, and clang-cl.exe. + +## Design ## + +The utf8.h API matches the string.h API as much as possible by design. There are a few major differences though. + +utf8.h uses char8_t* in C++ 20 instead of char* + +Anywhere in the string.h or strings.h documentation where it refers to 'bytes' I have changed that to utf8 codepoints. For instance, utf8len will return the number of utf8 codepoints in a utf8 string - which does not necessarily equate to the number of bytes. + +## API function docs ## + +```c +int utf8casecmp(const void *src1, const void *src2); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, `src1 == src2`, +`src1 > src2` respectively, case insensitive. + +```c +void *utf8cat(void *dst, const void *src); +``` +Append the utf8 string `src` onto the utf8 string `dst`. + +```c +void *utf8chr(const void *src, utf8_int32_t chr); +``` +Find the first match of the utf8 codepoint `chr` in the utf8 string `src`. + +```c +int utf8cmp(const void *src1, const void *src2); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, +`src1 == src2`, `src1 > src2` respectively. + +```c +void *utf8cpy(void *dst, const void *src); +``` +Copy the utf8 string `src` onto the memory allocated in `dst`. + +```c +size_t utf8cspn(const void *src, const void *reject); +``` +Number of utf8 codepoints in the utf8 string `src` that consists entirely +of utf8 codepoints not from the utf8 string `reject`. + +```c +void *utf8dup(const void *src); +``` +Duplicate the utf8 string `src` by getting its size, `malloc`ing a new buffer +copying over the data, and returning that. Or 0 if `malloc` failed. + +```c +size_t utf8len(const void *str); +``` +Number of utf8 codepoints in the utf8 string `str`, +**excluding** the null terminating byte. + +```c +size_t utf8nlen(const void *str, size_t n); +``` +Similar to `utf8len`, except that only at most `n` bytes of `src` are looked. + +```c +int utf8ncasecmp(const void *src1, const void *src2, size_t n); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, `src1 == src2`, +`src1 > src2` respectively, case insensitive. Checking at most `n` +bytes of each utf8 string. + +```c +void *utf8ncat(void *dst, const void *src, size_t n); +``` +Append the utf8 string `src` onto the utf8 string `dst`, +writing at most `n+1` bytes. Can produce an invalid utf8 +string if `n` falls partway through a utf8 codepoint. + +```c +int utf8ncmp(const void *src1, const void *src2, size_t n); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, +`src1 == src2`, `src1 > src2` respectively. Checking at most `n` +bytes of each utf8 string. + +```c +void *utf8ncpy(void *dst, const void *src, size_t n); +``` +Copy the utf8 string `src` onto the memory allocated in `dst`. +Copies at most `n` bytes. If `n` falls partway through a utf8 +codepoint, or if `dst` doesn't have enough room for a null +terminator, the final string will be cut short to preserve +utf8 validity. + +```c +void *utf8pbrk(const void *str, const void *accept); +``` +Locates the first occurrence in the utf8 string `str` of any byte in the +utf8 string `accept`, or 0 if no match was found. + +```c +void *utf8rchr(const void *src, utf8_int32_t chr); +``` +Find the last match of the utf8 codepoint `chr` in the utf8 string `src`. + +```c +size_t utf8size(const void *str); +``` +Number of bytes in the utf8 string `str`, +including the null terminating byte. + +```c +size_t utf8size_lazy(const void *str); +``` +Similar to `utf8size`, except that the null terminating byte is **excluded**. + +```c +size_t utf8nsize_lazy(const void *str, size_t n); +``` +Similar to `utf8size`, except that only at most `n` bytes of `src` are looked and +the null terminating byte is **excluded**. + +```c +size_t utf8spn(const void *src, const void *accept); +``` +Number of utf8 codepoints in the utf8 string `src` that consists entirely +of utf8 codepoints from the utf8 string `accept`. + +```c +void *utf8str(const void *haystack, const void *needle); +``` +The position of the utf8 string `needle` in the utf8 string `haystack`. + +```c +void *utf8casestr(const void *haystack, const void *needle); +``` +The position of the utf8 string `needle` in the utf8 string `haystack`, +case insensitive. + +```c +void *utf8valid(const void *str); +``` +Return 0 on success, or the position of the invalid utf8 codepoint on failure. + +```c +void *utf8nvalid(const void *str, size_t n); +``` +Similar to `utf8valid`, except that only at most `n` bytes of `src` are looked. + +```c +int utf8makevalid(void *str, utf8_int32_t replacement); +``` +Return 0 on success. Makes the `str` valid by replacing invalid sequences with +the 1-byte `replacement` codepoint. + +```c +void *utf8codepoint(const void *str, utf8_int32_t *out_codepoint); +``` +Sets out_codepoint to the current utf8 codepoint in `str`, and returns the +address of the next utf8 codepoint after the current one in `str`. + +```c +void *utf8rcodepoint(const void *str, utf8_int32_t *out_codepoint); +``` +Sets out_codepoint to the current utf8 codepoint in `str`, and returns the +address of the previous utf8 codepoint before the current one in `str`. + +```c +size_t utf8codepointsize(utf8_int32_t chr); +``` +Returns the size of the given codepoint in bytes. + +```c +void *utf8catcodepoint(void *utf8_restrict str, utf8_int32_t chr, size_t n); +``` +Write a codepoint to the given string, and return the address to the next +place after the written codepoint. Pass how many bytes left in the buffer to +n. If there is not enough space for the codepoint, this function returns +null. + +```c +int utf8islower(utf8_int32_t chr); +``` +Returns 1 if the given character is lowercase, or 0 if it is not. + +```c +int utf8isupper(utf8_int32_t chr); +``` +Returns 1 if the given character is uppercase, or 0 if it is not. + +```c +void utf8lwr(void *utf8_restrict str); +``` +Transform the given string into all lowercase codepoints. + +```c +void utf8upr(void *utf8_restrict str); +``` +Transform the given string into all uppercase codepoints. + +```c +utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); +``` +Make a codepoint lower case if possible. + +```c +utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); +``` +Make a codepoint upper case if possible. + +## Codepoint Case + +Various functions provided will do case insensitive compares, or transform utf8 +strings from one case to another. Given the vastness of unicode, and the authors +lack of understanding beyond latin codepoints on whether case means anything, +the following categories are the only ones that will be checked in case +insensitive code: + +* [ASCII](https://en.wikipedia.org/wiki/Basic_Latin_(Unicode_block)) +* [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)) +* [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) +* [Latin Extended-B](https://en.wikipedia.org/wiki/Latin_Extended-B) +* [Greek and Coptic](https://en.wikipedia.org/wiki/Greek_and_Coptic) +* [Cyrillic](https://en.wikipedia.org/wiki/Cyrillic_(Unicode_block)) + +## Todo ## + +- Implement utf8coll (akin to strcoll). +- Implement utf8fry (akin to strfry). +- Investigate adding dst buffer sizes for utf8cpy and utf8cat to catch overwrites (as suggested by [@FlohOfWoe](https://twitter.com/FlohOfWoe) in https://twitter.com/FlohOfWoe/status/618669237771608064) + +## License ## + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/include/utf8/appveyor.yml b/include/utf8/appveyor.yml new file mode 100644 index 0000000..cba55a0 --- /dev/null +++ b/include/utf8/appveyor.yml @@ -0,0 +1,51 @@ +version: '{build}' + +skip_tags: true +skip_branch_with_pr: true + +install: [] + +environment: + matrix: + - VSVERSION: Visual Studio 9 2008 + - VSVERSION: Visual Studio 10 2010 + - VSVERSION: Visual Studio 11 2012 + - VSVERSION: Visual Studio 12 2013 + - VSVERSION: Visual Studio 14 2015 + - VSVERSION: Visual Studio 15 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - VSVERSION: Visual Studio 16 2019 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + +platform: + - Win32 + - x64 + +matrix: + exclude: + - platform: x64 + VSVERSION: Visual Studio 9 2008 + # VS 2019 / 64-bit is tested in GitHub Actions instead. + - platform: x64 + VSVERSION: Visual Studio 16 2019 + +configuration: + - Debug + # Removed to reduce configuration explosion. + # - RelWithDebInfo + # - MinSizeRel + - Release + +build_script: + - md build + - cd build + - if NOT "%VSVERSION%"=="Visual Studio 16 2019" if "%PLATFORM%"=="x64" cmake -G "%VSVERSION% Win64" ../test + - if NOT "%VSVERSION%"=="Visual Studio 16 2019" if "%PLATFORM%"=="Win32" cmake -G "%VSVERSION%" ../test + - if "%VSVERSION%"=="Visual Studio 16 2019" cmake -G "%VSVERSION%" -A "%PLATFORM%" ../test + - msbuild /m /p:Configuration="%CONFIGURATION%" /p:Platform="%PLATFORM%" utf8.sln + - copy %CONFIGURATION%\utf8_test.exe utf8_test.exe + - copy %CONFIGURATION%\utf8_no_malloc_test.exe utf8_no_malloc_test.exe + +test_script: + - utf8_test.exe + - utf8_no_malloc_test.exe diff --git a/include/utf8/test/CMakeLists.txt b/include/utf8/test/CMakeLists.txt new file mode 100644 index 0000000..a89f926 --- /dev/null +++ b/include/utf8/test/CMakeLists.txt @@ -0,0 +1,117 @@ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +project(utf8) +cmake_minimum_required(VERSION 2.8.12) + +set(UTF8_USE_SANITIZER "" CACHE STRING "Set which Clang Sanitizer to use") + +macro(add_sanitizer target) + if(NOT "${UTF8_USE_SANITIZER}" STREQUAL "") + target_compile_options(${target} PUBLIC -fno-omit-frame-pointer -fsanitize=${UTF8_USE_SANITIZER}) + target_link_options(${target} PUBLIC -fno-omit-frame-pointer -fsanitize=${UTF8_USE_SANITIZER}) + endif() +endmacro() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_executable(utf8_test main.c) +add_sanitizer(utf8_test) + +add_executable(utf8_no_malloc_test no_malloc.c) +add_sanitizer(utf8_no_malloc_test) + +add_executable(utf8_test_c90 test.c) +add_sanitizer(utf8_test_c90) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c90 PUBLIC "-std=c90") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c90 PUBLIC "-std=c90") + endif() +endif() + +add_executable(utf8_test_c99 test.c) +add_sanitizer(utf8_test_c99) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c99 PUBLIC "-std=c99") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c99 PUBLIC "-std=c99") + endif() +endif() + +add_executable(utf8_test_c11 test.c) +add_sanitizer(utf8_test_c11) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c11 PUBLIC "-std=c11") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c11 PUBLIC "-std=c11") + endif() +endif() + +add_executable(utf8_test_cpp11 test.cpp) +add_sanitizer(utf8_test_cpp11) +set_target_properties(utf8_test_cpp11 PROPERTIES CXX_STANDARD 11) + +add_executable(utf8_test_cpp14 test.cpp) +add_sanitizer(utf8_test_cpp14) +set_target_properties(utf8_test_cpp14 PROPERTIES CXX_STANDARD 14) + +add_executable(utf8_test_cpp17 test.cpp) +add_sanitizer(utf8_test_cpp17) +set_target_properties(utf8_test_cpp17 PROPERTIES CXX_STANDARD 17) + +add_executable(utf8_test_cpp20 test.cpp) +add_sanitizer(utf8_test_cpp20) +set_target_properties(utf8_test_cpp20 PROPERTIES CXX_STANDARD 20) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Werror" + ) +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -Wno-c++98-compat" + ) +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514" + ) + if(${MSVC_VERSION} VERSION_GREATER "15.7") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "/Zc:__cplusplus" + ) + endif() +else() + message(WARNING "Unknown compiler '${CMAKE_C_COMPILER_ID}'!") +endif() diff --git a/include/utf8/test/main.c b/include/utf8/test/main.c new file mode 100644 index 0000000..3739fbe --- /dev/null +++ b/include/utf8/test/main.c @@ -0,0 +1,1669 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +// include the unit testing framework +#include "utest.h" + +// include the header we are testing +#include "utf8.h" + +const char data[] = { + '\xce', '\x93', '\xce', '\xb1', '\xce', '\xb6', '\xce', '\xad', '\xce', + '\xb5', '\xcf', '\x82', '\x20', '\xce', '\xba', '\xce', '\xb1', '\xe1', + '\xbd', '\xb6', '\x20', '\xce', '\xbc', '\xcf', '\x85', '\xcf', '\x81', + '\xcf', '\x84', '\xce', '\xb9', '\xe1', '\xbd', '\xb2', '\xcf', '\x82', + '\x20', '\xce', '\xb4', '\xe1', '\xbd', '\xb2', '\xce', '\xbd', '\x20', + '\xce', '\xb8', '\xe1', '\xbd', '\xb0', '\x20', '\xce', '\xb2', '\xcf', + '\x81', '\xe1', '\xbf', '\xb6', '\x20', '\xcf', '\x80', '\xce', '\xb9', + '\xe1', '\xbd', '\xb0', '\x20', '\xcf', '\x83', '\xcf', '\x84', '\xe1', + '\xbd', '\xb8', '\x20', '\xcf', '\x87', '\xcf', '\x81', '\xcf', '\x85', + '\xcf', '\x83', '\xce', '\xb1', '\xcf', '\x86', '\xe1', '\xbd', '\xb6', + '\x20', '\xce', '\xbe', '\xce', '\xad', '\xcf', '\x86', '\xcf', '\x89', + '\xcf', '\x84', '\xce', '\xbf', '\x0a', '\0'}; + +const char cmp[] = {'\xce', '\xbc', '\xcf', '\x85', '\0'}; + +const char lt[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', + '\xb6', '\xce', '\xac', '\0'}; + +const char gt[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', + '\xb6', '\xce', '\xae', '\0'}; + +const char spn[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', '\xb6', + '\xce', '\xad', '\xce', '\xb5', '\xcf', '\x82', + '\x20', '\xce', '\xba', '\0'}; + +const char pbrk[] = {'\xcf', '\x82', '\x20', '\xce', '\xb5', '\0'}; + +const char ascii1[] = "I lIke GOATS YARHAR."; +const char ascii2[] = "i LIKE goats yarHAR."; +const char allascii1[] = "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWYZ"; +const char allascii2[] = "ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwyz"; +const char haystack[] = "foobar"; +const char needle[] = "oba"; +const char endfailneedle[] = "ra"; +const char cspnmultisearch[] = "another test; string|one more"; +const char cspnmultidelims[] = "|;"; +const char spnasciisearch[] = ",,hello,,world"; +const char spnasciidelims[] = ","; + +struct LowerUpperPair { + int lower; + int upper; +}; + +const struct LowerUpperPair lowupPairs[] = { + /* ascii */ + {0x0061, 0x0041}, + {0x0062, 0x0042}, + {0x0063, 0x0043}, + {0x0064, 0x0044}, + {0x0065, 0x0045}, + {0x0066, 0x0046}, + {0x0067, 0x0047}, + {0x0068, 0x0048}, + {0x0069, 0x0049}, + {0x006a, 0x004a}, + {0x006b, 0x004b}, + {0x006c, 0x004c}, + {0x006d, 0x004d}, + {0x006e, 0x004e}, + {0x006f, 0x004f}, + {0x0070, 0x0050}, + {0x0071, 0x0051}, + {0x0072, 0x0052}, + {0x0073, 0x0053}, + {0x0074, 0x0054}, + {0x0075, 0x0055}, + {0x0076, 0x0056}, + {0x0077, 0x0057}, + {0x0078, 0x0058}, + {0x0079, 0x0059}, + {0x007a, 0x005a}, + + /* Latin-1 Supplement */ + {0x00e0, 0x00c0}, + {0x00e1, 0x00c1}, + {0x00e2, 0x00c2}, + {0x00e3, 0x00c3}, + {0x00e4, 0x00c4}, + {0x00e5, 0x00c5}, + {0x00e6, 0x00c6}, + {0x00e7, 0x00c7}, + {0x00e8, 0x00c8}, + {0x00e9, 0x00c9}, + {0x00ea, 0x00ca}, + {0x00eb, 0x00cb}, + {0x00ec, 0x00cc}, + {0x00ed, 0x00cd}, + {0x00ee, 0x00ce}, + {0x00ef, 0x00cf}, + {0x00f0, 0x00d0}, + {0x00f1, 0x00d1}, + {0x00f2, 0x00d2}, + {0x00f3, 0x00d3}, + {0x00f4, 0x00d4}, + {0x00f5, 0x00d5}, + {0x00f6, 0x00d6}, + {0x00f8, 0x00d8}, + {0x00f9, 0x00d9}, + {0x00fa, 0x00da}, + {0x00fb, 0x00db}, + {0x00fc, 0x00dc}, + {0x00fd, 0x00dd}, + {0x00fe, 0x00de}, + {0x00ff, 0x0178}, + + /* Latin Extended-A */ + {0x0101, 0x0100}, + {0x0103, 0x0102}, + {0x0105, 0x0104}, + {0x0107, 0x0106}, + {0x0109, 0x0108}, + {0x010b, 0x010a}, + {0x010d, 0x010c}, + {0x010f, 0x010e}, + {0x0111, 0x0110}, + {0x0113, 0x0112}, + {0x0115, 0x0114}, + {0x0117, 0x0116}, + {0x0119, 0x0118}, + {0x011b, 0x011a}, + {0x011d, 0x011c}, + {0x011f, 0x011e}, + {0x0121, 0x0120}, + {0x0123, 0x0122}, + {0x0125, 0x0124}, + {0x0127, 0x0126}, + {0x0129, 0x0128}, + {0x012b, 0x012a}, + {0x012d, 0x012c}, + {0x012f, 0x012e}, + {0x0133, 0x0132}, + {0x0135, 0x0134}, + {0x0137, 0x0136}, + {0x013a, 0x0139}, + {0x013c, 0x013b}, + {0x013e, 0x013d}, + {0x0140, 0x013f}, + {0x0142, 0x0141}, + {0x0144, 0x0143}, + {0x0146, 0x0145}, + {0x0148, 0x0147}, + {0x014b, 0x014a}, + {0x014d, 0x014c}, + {0x014f, 0x014e}, + {0x0151, 0x0150}, + {0x0153, 0x0152}, + {0x0155, 0x0154}, + {0x0157, 0x0156}, + {0x0159, 0x0158}, + {0x015b, 0x015a}, + {0x015d, 0x015c}, + {0x015f, 0x015e}, + {0x0161, 0x0160}, + {0x0163, 0x0162}, + {0x0165, 0x0164}, + {0x0167, 0x0166}, + {0x0169, 0x0168}, + {0x016b, 0x016a}, + {0x016d, 0x016c}, + {0x016f, 0x016e}, + {0x0171, 0x0170}, + {0x0173, 0x0172}, + {0x0175, 0x0174}, + {0x0177, 0x0176}, + {0x017a, 0x0179}, + {0x017c, 0x017b}, + {0x017e, 0x017d}, + + /* Latin Extended-B */ + {0x0180, 0x0243}, + {0x01dd, 0x018e}, + {0x019a, 0x023d}, + {0x019e, 0x0220}, + {0x0292, 0x01b7}, + {0x01c6, 0x01c4}, + {0x01c9, 0x01c7}, + {0x01cc, 0x01ca}, + {0x01f3, 0x01f1}, + {0x01bf, 0x01f7}, + {0x0183, 0x0182}, + {0x0185, 0x0184}, + {0x0188, 0x0187}, + {0x018c, 0x018b}, + {0x0192, 0x0191}, + {0x0199, 0x0198}, + {0x01a1, 0x01a0}, + {0x01a3, 0x01a2}, + {0x01a5, 0x01a4}, + {0x01a8, 0x01a7}, + {0x01ad, 0x01ac}, + {0x01b0, 0x01af}, + {0x01b4, 0x01b3}, + {0x01b6, 0x01b5}, + {0x01b9, 0x01b8}, + {0x01bd, 0x01bc}, + {0x01ce, 0x01cd}, + {0x01d0, 0x01cf}, + {0x01d2, 0x01d1}, + {0x01d4, 0x01d3}, + {0x01d6, 0x01d5}, + {0x01d8, 0x01d7}, + {0x01da, 0x01d9}, + {0x01dc, 0x01db}, + {0x01df, 0x01de}, + {0x01e1, 0x01e0}, + {0x01e3, 0x01e2}, + {0x01e5, 0x01e4}, + {0x01e7, 0x01e6}, + {0x01e9, 0x01e8}, + {0x01eb, 0x01ea}, + {0x01ed, 0x01ec}, + {0x01ef, 0x01ee}, + {0x01f5, 0x01f4}, + {0x01f9, 0x01f8}, + {0x01fb, 0x01fa}, + {0x01fd, 0x01fc}, + {0x01ff, 0x01fe}, + {0x0201, 0x0200}, + {0x0203, 0x0202}, + {0x0205, 0x0204}, + {0x0207, 0x0206}, + {0x0209, 0x0208}, + {0x020b, 0x020a}, + {0x020d, 0x020c}, + {0x020f, 0x020e}, + {0x0211, 0x0210}, + {0x0213, 0x0212}, + {0x0215, 0x0214}, + {0x0217, 0x0216}, + {0x0219, 0x0218}, + {0x021b, 0x021a}, + {0x021d, 0x021c}, + {0x021f, 0x021e}, + {0x0223, 0x0222}, + {0x0225, 0x0224}, + {0x0227, 0x0226}, + {0x0229, 0x0228}, + {0x022b, 0x022a}, + {0x022d, 0x022c}, + {0x022f, 0x022e}, + {0x0231, 0x0230}, + {0x0233, 0x0232}, + {0x023c, 0x023b}, + {0x0242, 0x0241}, + {0x0247, 0x0246}, + {0x0249, 0x0248}, + {0x024b, 0x024a}, + {0x024d, 0x024c}, + {0x024f, 0x024e}, + + /* Greek and Coptic */ + {0x037b, 0x03fd}, + {0x037c, 0x03fe}, + {0x037d, 0x03ff}, + + {0x03f3, 0x037f}, + {0x03ac, 0x0386}, + + {0x03ad, 0x0388}, + {0x03ae, 0x0389}, + {0x03af, 0x038a}, + + {0x03cc, 0x038c}, + + {0x03cd, 0x038e}, + {0x03ce, 0x038f}, + + {0x0371, 0x0370}, + {0x0373, 0x0372}, + {0x0377, 0x0376}, + + {0x03B1, 0x0391}, + {0x03B2, 0x0392}, + {0x03B3, 0x0393}, + {0x03B4, 0x0394}, + {0x03B5, 0x0395}, + {0x03B6, 0x0396}, + {0x03B7, 0x0397}, + {0x03B8, 0x0398}, + {0x03B9, 0x0399}, + {0x03BA, 0x039A}, + {0x03BB, 0x039B}, + {0x03BC, 0x039C}, + {0x03BD, 0x039D}, + {0x03BE, 0x039E}, + {0x03BF, 0x039F}, + {0x03C0, 0x03A0}, + {0x03C1, 0x03A1}, + + {0x03C3, 0x03A3}, + {0x03C4, 0x03A4}, + {0x03C5, 0x03A5}, + {0x03C6, 0x03A6}, + {0x03C7, 0x03A7}, + {0x03C8, 0x03A8}, + {0x03C9, 0x03A9}, + {0x03ca, 0x03aa}, + {0x03cb, 0x03ab}, + + {0x03d1, 0x03f4}, + + {0x03d7, 0x03cf}, + + {0x03d9, 0x03d8}, + {0x03db, 0x03da}, + {0x03dd, 0x03dc}, + {0x03df, 0x03de}, + {0x03e1, 0x03e0}, + {0x03e3, 0x03e2}, + {0x03e5, 0x03e4}, + {0x03e7, 0x03e6}, + {0x03e9, 0x03e8}, + {0x03eb, 0x03ea}, + {0x03ed, 0x03ec}, + {0x03ef, 0x03ee}, + + {0x03f2, 0x03f9}, + + {0x03f8, 0x03f7}, + + {0x03fb, 0x03fa}, + + /* Cyrillic */ + {0x0450, 0x0400}, + {0x0451, 0x0401}, + {0x0452, 0x0402}, + {0x0453, 0x0403}, + {0x0454, 0x0404}, + {0x0455, 0x0405}, + {0x0456, 0x0406}, + {0x0457, 0x0407}, + {0x0458, 0x0408}, + {0x0459, 0x0409}, + {0x045a, 0x040a}, + {0x045b, 0x040b}, + {0x045c, 0x040c}, + {0x045d, 0x040d}, + {0x045e, 0x040e}, + {0x045f, 0x040f}, + + {0x0430, 0x0410}, + {0x0431, 0x0411}, + {0x0432, 0x0412}, + {0x0433, 0x0413}, + {0x0434, 0x0414}, + {0x0435, 0x0415}, + {0x0436, 0x0416}, + {0x0437, 0x0417}, + {0x0438, 0x0418}, + {0x0439, 0x0419}, + {0x043a, 0x041a}, + {0x043b, 0x041b}, + {0x043c, 0x041c}, + {0x043d, 0x041d}, + {0x043e, 0x041e}, + {0x043f, 0x041f}, + {0x0440, 0x0420}, + {0x0441, 0x0421}, + {0x0442, 0x0422}, + {0x0443, 0x0423}, + {0x0444, 0x0424}, + {0x0445, 0x0425}, + {0x0446, 0x0426}, + {0x0447, 0x0427}, + {0x0448, 0x0428}, + {0x0449, 0x0429}, + {0x044a, 0x042a}, + {0x044b, 0x042b}, + {0x044c, 0x042c}, + {0x044d, 0x042d}, + {0x044e, 0x042e}, + {0x044f, 0x042f}, + + {0x0461, 0x0460}, + {0x0463, 0x0462}, + {0x0465, 0x0464}, + {0x0467, 0x0466}, + {0x0469, 0x0468}, + {0x046b, 0x046a}, + {0x046d, 0x046c}, + {0x046f, 0x046e}, + {0x0471, 0x0470}, + {0x0473, 0x0472}, + {0x0475, 0x0474}, + {0x0477, 0x0476}, + {0x0479, 0x0478}, + {0x047b, 0x047a}, + {0x047d, 0x047c}, + {0x047f, 0x047e}, + {0x0481, 0x0480}, + + {0x048b, 0x048a}, + {0x048d, 0x048c}, + {0x048f, 0x048e}, + {0x0491, 0x0490}, + {0x0493, 0x0492}, + {0x0495, 0x0494}, + {0x0497, 0x0496}, + {0x0499, 0x0498}, + {0x049b, 0x049a}, + {0x049d, 0x049c}, + {0x049f, 0x049e}, + {0x04a1, 0x04a0}, + {0x04a3, 0x04a2}, + {0x04a5, 0x04a4}, + {0x04a7, 0x04a6}, + {0x04a9, 0x04a8}, + {0x04ab, 0x04aa}, + {0x04ad, 0x04ac}, + {0x04af, 0x04ae}, + {0x04b1, 0x04b0}, + {0x04b3, 0x04b2}, + {0x04b5, 0x04b4}, + {0x04b7, 0x04b6}, + {0x04b9, 0x04b8}, + {0x04bb, 0x04ba}, + {0x04bd, 0x04bc}, + {0x04bf, 0x04be}, + {0x04c1, 0x04c0}, + {0x04c3, 0x04c2}, + {0x04c5, 0x04c4}, + {0x04c7, 0x04c6}, + {0x04c9, 0x04c8}, + {0x04cb, 0x04ca}, + {0x04cd, 0x04cc}, + {0x04cf, 0x04ce}, + {0x04d1, 0x04d0}, + {0x04d3, 0x04d2}, + {0x04d5, 0x04d4}, + {0x04d7, 0x04d6}, + {0x04d9, 0x04d8}, + {0x04db, 0x04da}, + {0x04dd, 0x04dc}, + {0x04df, 0x04de}, + {0x04e1, 0x04e0}, + {0x04e3, 0x04e2}, + {0x04e5, 0x04e4}, + {0x04e7, 0x04e6}, + {0x04e9, 0x04e8}, + {0x04eb, 0x04ea}, + {0x04ed, 0x04ec}, + {0x04ef, 0x04ee}, + {0x04f1, 0x04f0}, + {0x04f3, 0x04f2}, + {0x04f5, 0x04f4}, + {0x04f7, 0x04f6}, + {0x04f9, 0x04f8}, + {0x04fb, 0x04fa}, + {0x04fd, 0x04fc}, + {0x04ff, 0x04fe}, + + // End of array marker + {0, 0}}; + +const char lowersStr[] = { + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', + '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', + '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7a', '\xc3', + '\xa0', '\xc3', '\xa1', '\xc3', '\xa2', '\xc3', '\xa3', '\xc3', '\xa4', + '\xc3', '\xa5', '\xc3', '\xa6', '\xc3', '\xa7', '\xc3', '\xa8', '\xc3', + '\xa9', '\xc3', '\xaa', '\xc3', '\xab', '\xc3', '\xac', '\xc3', '\xad', + '\xc3', '\xae', '\xc3', '\xaf', '\xc3', '\xb0', '\xc3', '\xb1', '\xc3', + '\xb2', '\xc3', '\xb3', '\xc3', '\xb4', '\xc3', '\xb5', '\xc3', '\xb6', + '\xc3', '\xb8', '\xc3', '\xb9', '\xc3', '\xba', '\xc3', '\xbb', '\xc3', + '\xbc', '\xc3', '\xbd', '\xc3', '\xbe', '\xc3', '\xbf', '\xc4', '\x81', + '\xc4', '\x83', '\xc4', '\x85', '\xc4', '\x87', '\xc4', '\x89', '\xc4', + '\x8b', '\xc4', '\x8d', '\xc4', '\x8f', '\xc4', '\x91', '\xc4', '\x93', + '\xc4', '\x95', '\xc4', '\x97', '\xc4', '\x99', '\xc4', '\x9b', '\xc4', + '\x9d', '\xc4', '\x9f', '\xc4', '\xa1', '\xc4', '\xa3', '\xc4', '\xa5', + '\xc4', '\xa7', '\xc4', '\xa9', '\xc4', '\xab', '\xc4', '\xad', '\xc4', + '\xaf', '\xc4', '\xb3', '\xc4', '\xb5', '\xc4', '\xb7', '\xc4', '\xba', + '\xc4', '\xbc', '\xc4', '\xbe', '\xc5', '\x80', '\xc5', '\x82', '\xc5', + '\x84', '\xc5', '\x86', '\xc5', '\x88', '\xc5', '\x8b', '\xc5', '\x8d', + '\xc5', '\x8f', '\xc5', '\x91', '\xc5', '\x93', '\xc5', '\x95', '\xc5', + '\x97', '\xc5', '\x99', '\xc5', '\x9b', '\xc5', '\x9d', '\xc5', '\x9f', + '\xc5', '\xa1', '\xc5', '\xa3', '\xc5', '\xa5', '\xc5', '\xa7', '\xc5', + '\xa9', '\xc5', '\xab', '\xc5', '\xad', '\xc5', '\xaf', '\xc5', '\xb1', + '\xc5', '\xb3', '\xc5', '\xb5', '\xc5', '\xb7', '\xc5', '\xba', '\xc5', + '\xbc', '\xc5', '\xbe', '\xc6', '\x80', '\xc7', '\x9d', '\xc6', '\x9a', + '\xc6', '\x9e', '\xca', '\x92', '\xc7', '\x86', '\xc7', '\x89', '\xc7', + '\x8c', '\xc7', '\xb3', '\xc6', '\xbf', '\xc6', '\x83', '\xc6', '\x85', + '\xc6', '\x88', '\xc6', '\x8c', '\xc6', '\x92', '\xc6', '\x99', '\xc6', + '\xa1', '\xc6', '\xa3', '\xc6', '\xa5', '\xc6', '\xa8', '\xc6', '\xad', + '\xc6', '\xb0', '\xc6', '\xb4', '\xc6', '\xb6', '\xc6', '\xb9', '\xc6', + '\xbd', '\xc7', '\x8e', '\xc7', '\x90', '\xc7', '\x92', '\xc7', '\x94', + '\xc7', '\x96', '\xc7', '\x98', '\xc7', '\x9a', '\xc7', '\x9c', '\xc7', + '\x9f', '\xc7', '\xa1', '\xc7', '\xa3', '\xc7', '\xa5', '\xc7', '\xa7', + '\xc7', '\xa9', '\xc7', '\xab', '\xc7', '\xad', '\xc7', '\xaf', '\xc7', + '\xb5', '\xc7', '\xb9', '\xc7', '\xbb', '\xc7', '\xbd', '\xc7', '\xbf', + '\xc8', '\x81', '\xc8', '\x83', '\xc8', '\x85', '\xc8', '\x87', '\xc8', + '\x89', '\xc8', '\x8b', '\xc8', '\x8d', '\xc8', '\x8f', '\xc8', '\x91', + '\xc8', '\x93', '\xc8', '\x95', '\xc8', '\x97', '\xc8', '\x99', '\xc8', + '\x9b', '\xc8', '\x9d', '\xc8', '\x9f', '\xc8', '\xa3', '\xc8', '\xa5', + '\xc8', '\xa7', '\xc8', '\xa9', '\xc8', '\xab', '\xc8', '\xad', '\xc8', + '\xaf', '\xc8', '\xb1', '\xc8', '\xb3', '\xc8', '\xbc', '\xc9', '\x82', + '\xc9', '\x87', '\xc9', '\x89', '\xc9', '\x8b', '\xc9', '\x8d', '\xc9', + '\x8f', '\xcd', '\xbb', '\xcd', '\xbc', '\xcd', '\xbd', '\xcf', '\xb3', + '\xce', '\xac', '\xce', '\xad', '\xce', '\xae', '\xce', '\xaf', '\xcf', + '\x8c', '\xcf', '\x8d', '\xcf', '\x8e', '\xcd', '\xb1', '\xcd', '\xb3', + '\xcd', '\xb7', '\xce', '\xb1', '\xce', '\xb2', '\xce', '\xb3', '\xce', + '\xb4', '\xce', '\xb5', '\xce', '\xb6', '\xce', '\xb7', '\xce', '\xb8', + '\xce', '\xb9', '\xce', '\xba', '\xce', '\xbb', '\xce', '\xbc', '\xce', + '\xbd', '\xce', '\xbe', '\xce', '\xbf', '\xcf', '\x80', '\xcf', '\x81', + '\xcf', '\x83', '\xcf', '\x84', '\xcf', '\x85', '\xcf', '\x86', '\xcf', + '\x87', '\xcf', '\x88', '\xcf', '\x89', '\xcf', '\x8a', '\xcf', '\x8b', + '\xcf', '\x97', '\xcf', '\x99', '\xcf', '\x9b', '\xcf', '\x9d', '\xcf', + '\x9f', '\xcf', '\xa1', '\xcf', '\xa3', '\xcf', '\xa5', '\xcf', '\xa7', + '\xcf', '\xa9', '\xcf', '\xab', '\xcf', '\xad', '\xcf', '\xaf', '\xcf', + '\xb2', '\xcf', '\xb8', '\xcf', '\xbb', '\xd0', '\xb0', '\xd0', '\xb1', + '\xd0', '\xb2', '\xd0', '\xb3', '\xd0', '\xb4', '\xd0', '\xb5', '\xd1', + '\x91', '\xd0', '\xb6', '\xd0', '\xb7', '\xd0', '\xb8', '\xd0', '\xb9', + '\xd0', '\xba', '\xd0', '\xbb', '\xd0', '\xbc', '\xd0', '\xbd', '\xd0', + '\xbe', '\xd0', '\xbf', '\xd1', '\x80', '\xd1', '\x81', '\xd1', '\x82', + '\xd1', '\x83', '\xd1', '\x84', '\xd1', '\x85', '\xd1', '\x86', '\xd1', + '\x87', '\xd1', '\x88', '\xd1', '\x89', '\xd1', '\x8a', '\xd1', '\x8b', + '\xd1', '\x8c', '\xd1', '\x8d', '\xd1', '\x8e', '\xd1', '\x8f', '\0'}; + +const char uppersStr[] = { + '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', '\x48', '\x49', + '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', '\x51', '\x52', + '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', '\x5a', '\xc3', + '\x80', '\xc3', '\x81', '\xc3', '\x82', '\xc3', '\x83', '\xc3', '\x84', + '\xc3', '\x85', '\xc3', '\x86', '\xc3', '\x87', '\xc3', '\x88', '\xc3', + '\x89', '\xc3', '\x8a', '\xc3', '\x8b', '\xc3', '\x8c', '\xc3', '\x8d', + '\xc3', '\x8e', '\xc3', '\x8f', '\xc3', '\x90', '\xc3', '\x91', '\xc3', + '\x92', '\xc3', '\x93', '\xc3', '\x94', '\xc3', '\x95', '\xc3', '\x96', + '\xc3', '\x98', '\xc3', '\x99', '\xc3', '\x9a', '\xc3', '\x9b', '\xc3', + '\x9c', '\xc3', '\x9d', '\xc3', '\x9e', '\xc5', '\xb8', '\xc4', '\x80', + '\xc4', '\x82', '\xc4', '\x84', '\xc4', '\x86', '\xc4', '\x88', '\xc4', + '\x8a', '\xc4', '\x8c', '\xc4', '\x8e', '\xc4', '\x90', '\xc4', '\x92', + '\xc4', '\x94', '\xc4', '\x96', '\xc4', '\x98', '\xc4', '\x9a', '\xc4', + '\x9c', '\xc4', '\x9e', '\xc4', '\xa0', '\xc4', '\xa2', '\xc4', '\xa4', + '\xc4', '\xa6', '\xc4', '\xa8', '\xc4', '\xaa', '\xc4', '\xac', '\xc4', + '\xae', '\xc4', '\xb2', '\xc4', '\xb4', '\xc4', '\xb6', '\xc4', '\xb9', + '\xc4', '\xbb', '\xc4', '\xbd', '\xc4', '\xbf', '\xc5', '\x81', '\xc5', + '\x83', '\xc5', '\x85', '\xc5', '\x87', '\xc5', '\x8a', '\xc5', '\x8c', + '\xc5', '\x8e', '\xc5', '\x90', '\xc5', '\x92', '\xc5', '\x94', '\xc5', + '\x96', '\xc5', '\x98', '\xc5', '\x9a', '\xc5', '\x9c', '\xc5', '\x9e', + '\xc5', '\xa0', '\xc5', '\xa2', '\xc5', '\xa4', '\xc5', '\xa6', '\xc5', + '\xa8', '\xc5', '\xaa', '\xc5', '\xac', '\xc5', '\xae', '\xc5', '\xb0', + '\xc5', '\xb2', '\xc5', '\xb4', '\xc5', '\xb6', '\xc5', '\xb9', '\xc5', + '\xbb', '\xc5', '\xbd', '\xc9', '\x83', '\xc6', '\x8e', '\xc8', '\xbd', + '\xc8', '\xa0', '\xc6', '\xb7', '\xc7', '\x84', '\xc7', '\x87', '\xc7', + '\x8a', '\xc7', '\xb1', '\xc7', '\xb7', '\xc6', '\x82', '\xc6', '\x84', + '\xc6', '\x87', '\xc6', '\x8b', '\xc6', '\x91', '\xc6', '\x98', '\xc6', + '\xa0', '\xc6', '\xa2', '\xc6', '\xa4', '\xc6', '\xa7', '\xc6', '\xac', + '\xc6', '\xaf', '\xc6', '\xb3', '\xc6', '\xb5', '\xc6', '\xb8', '\xc6', + '\xbc', '\xc7', '\x8d', '\xc7', '\x8f', '\xc7', '\x91', '\xc7', '\x93', + '\xc7', '\x95', '\xc7', '\x97', '\xc7', '\x99', '\xc7', '\x9b', '\xc7', + '\x9e', '\xc7', '\xa0', '\xc7', '\xa2', '\xc7', '\xa4', '\xc7', '\xa6', + '\xc7', '\xa8', '\xc7', '\xaa', '\xc7', '\xac', '\xc7', '\xae', '\xc7', + '\xb4', '\xc7', '\xb8', '\xc7', '\xba', '\xc7', '\xbc', '\xc7', '\xbe', + '\xc8', '\x80', '\xc8', '\x82', '\xc8', '\x84', '\xc8', '\x86', '\xc8', + '\x88', '\xc8', '\x8a', '\xc8', '\x8c', '\xc8', '\x8e', '\xc8', '\x90', + '\xc8', '\x92', '\xc8', '\x94', '\xc8', '\x96', '\xc8', '\x98', '\xc8', + '\x9a', '\xc8', '\x9c', '\xc8', '\x9e', '\xc8', '\xa2', '\xc8', '\xa4', + '\xc8', '\xa6', '\xc8', '\xa8', '\xc8', '\xaa', '\xc8', '\xac', '\xc8', + '\xae', '\xc8', '\xb0', '\xc8', '\xb2', '\xc8', '\xbb', '\xc9', '\x81', + '\xc9', '\x86', '\xc9', '\x88', '\xc9', '\x8a', '\xc9', '\x8c', '\xc9', + '\x8e', '\xcf', '\xbd', '\xcf', '\xbe', '\xcf', '\xbf', '\xcd', '\xbf', + '\xce', '\x86', '\xce', '\x88', '\xce', '\x89', '\xce', '\x8a', '\xce', + '\x8c', '\xce', '\x8e', '\xce', '\x8f', '\xcd', '\xb0', '\xcd', '\xb2', + '\xcd', '\xb6', '\xce', '\x91', '\xce', '\x92', '\xce', '\x93', '\xce', + '\x94', '\xce', '\x95', '\xce', '\x96', '\xce', '\x97', '\xce', '\x98', + '\xce', '\x99', '\xce', '\x9a', '\xce', '\x9b', '\xce', '\x9c', '\xce', + '\x9d', '\xce', '\x9e', '\xce', '\x9f', '\xce', '\xa0', '\xce', '\xa1', + '\xce', '\xa3', '\xce', '\xa4', '\xce', '\xa5', '\xce', '\xa6', '\xce', + '\xa7', '\xce', '\xa8', '\xce', '\xa9', '\xce', '\xaa', '\xce', '\xab', + '\xcf', '\x8f', '\xcf', '\x98', '\xcf', '\x9a', '\xcf', '\x9c', '\xcf', + '\x9e', '\xcf', '\xa0', '\xcf', '\xa2', '\xcf', '\xa4', '\xcf', '\xa6', + '\xcf', '\xa8', '\xcf', '\xaa', '\xcf', '\xac', '\xcf', '\xae', '\xcf', + '\xb9', '\xcf', '\xb7', '\xcf', '\xba', '\xd0', '\x90', '\xd0', '\x91', + '\xd0', '\x92', '\xd0', '\x93', '\xd0', '\x94', '\xd0', '\x95', '\xd0', + '\x81', '\xd0', '\x96', '\xd0', '\x97', '\xd0', '\x98', '\xd0', '\x99', + '\xd0', '\x9a', '\xd0', '\x9b', '\xd0', '\x9c', '\xd0', '\x9d', '\xd0', + '\x9e', '\xd0', '\x9f', '\xd0', '\xa0', '\xd0', '\xa1', '\xd0', '\xa2', + '\xd0', '\xa3', '\xd0', '\xa4', '\xd0', '\xa5', '\xd0', '\xa6', '\xd0', + '\xa7', '\xd0', '\xa8', '\xd0', '\xa9', '\xd0', '\xaa', '\xd0', '\xab', + '\xd0', '\xac', '\xd0', '\xad', '\xd0', '\xae', '\xd0', '\xaf', '\0'}; + +UTEST(utf8len, data) { ASSERT_EQ(53, utf8len(data)); } + +UTEST(utf8nlen, data) { ASSERT_EQ(52, utf8nlen(data, 103)); } + +UTEST(utf8cat, empty_cat_data) { + char cat[512] = {'\0'}; + + ASSERT_EQ(0, utf8len(cat)); + + ASSERT_EQ(53, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, one_byte_cat_data) { + char cat[512]; + + cat[0] = 'a'; + cat[1] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, two_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xce'; + cat[1] = '\x93'; + cat[2] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, three_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xe1'; + cat[1] = '\xbd'; + cat[2] = '\xb6'; + cat[3] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, four_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xf0'; + cat[1] = '\x90'; + cat[2] = '\x8d'; + cat[3] = '\x88'; + cat[4] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, cat_data_data) { + char cat[512] = {'\0'}; + + ASSERT_EQ(0, utf8len(cat)); + + ASSERT_EQ(106, utf8len(utf8cat(utf8cat(cat, data), data))); +} + +UTEST(utf8str, cmp) { ASSERT_EQ(data + 21, utf8str(data, cmp)); } + +UTEST(utf8str, test) { ASSERT_EQ((void *)0, utf8str(data, "test")); } + +UTEST(utf8str, empty) { ASSERT_EQ(data, utf8str(data, "")); } + +UTEST(utf8str, partial) { ASSERT_EQ(haystack + 2, utf8str(haystack, needle)); } + +UTEST(utf8str, endfail) { + ASSERT_EQ((void *)0, utf8str(haystack, endfailneedle)); +} + +UTEST(utf8casestr, cmp) { ASSERT_EQ(data + 21, utf8casestr(data, cmp)); } + +UTEST(utf8casestr, test) { ASSERT_EQ((void *)0, utf8casestr(data, "test")); } + +UTEST(utf8casestr, empty) { ASSERT_EQ(data, utf8casestr(data, "")); } + +UTEST(utf8casestr, partial) { + ASSERT_EQ(haystack + 2, utf8casestr(haystack, needle)); +} + +UTEST(utf8casestr, endfail) { + ASSERT_EQ((void *)0, utf8casestr(haystack, endfailneedle)); +} + +UTEST(utf8casestr, latin) { + ASSERT_EQ(lowersStr, utf8casestr(lowersStr, uppersStr)); +} + +UTEST(utf8chr, a) { ASSERT_EQ(data + 21, utf8chr(data, 0x3bc)); } + +UTEST(utf8chr, b) { ASSERT_EQ(NULL, utf8chr(data, 0x20ac)); } + +UTEST(utf8chr, null_terminator) { ASSERT_EQ(data + 104, utf8chr(data, '\0')); } + +UTEST(utf8chr, 0x20) { ASSERT_EQ(data + 12, utf8chr(data, 0x20)); } + +UTEST(utf8cmp, lt) { ASSERT_LT(0, utf8cmp(data, lt)); } + +UTEST(utf8cmp, eq) { ASSERT_EQ(0, utf8cmp(data, data)); } + +UTEST(utf8cmp, gt) { ASSERT_GT(0, utf8cmp(data, gt)); } + +UTEST(utf8cpy, data) { + char cpy[512] = {'\0'}; + + ASSERT_EQ(53, utf8len(utf8cpy(cpy, data))); +} + +// Matches \xce\x93 \xce\xb1 \xce\xb6 \xce\xad \xce\xb5 \xcf\x82 \x20 \xce\xba +// \xce\xb1 +UTEST(utf8spn, spn) { ASSERT_EQ(9, utf8spn(data, spn)); } + +UTEST(utf8spn, data) { ASSERT_EQ(53, utf8spn(data, data)); } + +UTEST(utf8spn, ascii) { ASSERT_EQ(0, utf8spn(data, "ab")); } + +UTEST(utf8spn, spnasciisearch) { + ASSERT_EQ(2, utf8spn(spnasciisearch, spnasciidelims)); +} + +UTEST(utf8cspn, spn) { ASSERT_EQ(0, utf8cspn(data, spn)); } + +UTEST(utf8cspn, data) { ASSERT_EQ(0, utf8cspn(data, data)); } + +UTEST(utf8cspn, ascii) { ASSERT_EQ(53, utf8cspn(data, "ab")); } + +UTEST(utf8cspn, cspnmultisearch) { + ASSERT_EQ(12, utf8cspn(cspnmultisearch, cspnmultidelims)); +} + +UTEST(utf8rchr, a) { ASSERT_EQ(data + 21, utf8rchr(data, 0x3bc)); } + +UTEST(utf8rchr, b) { ASSERT_EQ(NULL, utf8rchr(data, 0x20ac)); } + +UTEST(utf8rchr, null_terminator) { + ASSERT_EQ(data + 104, utf8rchr(data, '\0')); +} + +UTEST(utf8rchr, 0x20) { ASSERT_EQ(data + 90, utf8rchr(data, 0x20)); } + +UTEST(utf8rchr, overrun) { + const char ascii[] = "Hello\0Hello "; + ASSERT_EQ(4, utf8rchr(ascii, 'o') - ascii); +} + +UTEST(utf8rchr, underrun) { + const char ascii[] = "Helloo"; + ASSERT_EQ(5, utf8rchr(ascii, 'o') - ascii); +} + +UTEST(utf8dup, data) { + void *const dup = utf8dup(data); + ASSERT_TRUE(dup); + ASSERT_EQ(53, utf8len(dup)); + free(dup); +} + +UTEST(utf8dup, ascii) { + void *const dup = utf8dup("ab"); + ASSERT_TRUE(dup); + ASSERT_EQ(2, utf8len(dup)); + free(dup); +} + +UTEST(utf8dup, empty) { + void *const dup = utf8dup(""); + ASSERT_TRUE(dup); + ASSERT_EQ(0, utf8len(dup)); + free(dup); +} + +UTEST(utf8ndup, ascii) { + void *const dup = utf8ndup("1234567890", 4); + ASSERT_TRUE(dup); + ASSERT_EQ(4, utf8len(dup)); + free(dup); +} + +UTEST(utf8ndup, ascii_larger) { + void *const dup = utf8ndup("1234567890", 100); + ASSERT_TRUE(dup); + ASSERT_EQ(10, utf8len(dup)); + free(dup); +} + +static utf8_int8_t *allocate_from_buffer(utf8_int8_t *user_data, size_t n) { + return user_data; +} + +UTEST(utf8dup_ex, ascii) { + char user_data[1024]; + void *const dup = utf8dup_ex("1234567890", allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(10, utf8len(dup)); +} + +UTEST(utf8ndup_ex, ascii) { + char user_data[1024]; + void *const dup = + utf8ndup_ex("1234567890", 4, allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(4, utf8len(dup)); +} + +UTEST(utf8size, data) { ASSERT_EQ(105, utf8size(data)); } + +UTEST(utf8size, ascii) { ASSERT_EQ(3, utf8size("ab")); } + +UTEST(utf8size, empty) { ASSERT_EQ(1, utf8size("")); } + +UTEST(utf8size_lazy, data) { ASSERT_EQ(104, utf8size_lazy(data)); } + +UTEST(utf8size_lazy, ascii) { ASSERT_EQ(2, utf8size_lazy("ab")); } + +UTEST(utf8size_lazy, empty) { ASSERT_EQ(0, utf8size_lazy("")); } + +UTEST(utf8nsize_lazy, data) { ASSERT_EQ(50, utf8nsize_lazy(data, 50)); } + +UTEST(utf8nsize_lazy, ascii) { ASSERT_EQ(2, utf8nsize_lazy("ab", 50)); } + +UTEST(utf8nsize_lazy, empty) { ASSERT_EQ(0, utf8nsize_lazy("", 50)); } + +UTEST(utf8valid, a) { + char invalid[6]; + + invalid[0] = '\xf0'; + invalid[1] = '\x8f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, b) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, c) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, d) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\x3f'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, e) { + char invalid[6]; + + invalid[0] = '\xe0'; + invalid[1] = '\x9f'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, f) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, g) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, h) { + char invalid[6]; + + invalid[0] = '\xc1'; + invalid[1] = '\xbf'; + invalid[2] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, i) { + char invalid[6]; + + invalid[0] = '\xdf'; + invalid[1] = '\x3f'; + invalid[2] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, j) { + char invalid[6]; + + invalid[0] = '\x80'; + invalid[1] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, k) { + char invalid[6]; + + invalid[0] = '\xf8'; + invalid[1] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, l) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\xbf'; + invalid[5] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, m) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, n) { + char invalid[6]; + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, data) { ASSERT_EQ(NULL, utf8valid(data)); } + +UTEST(utf8valid, ascii) { ASSERT_EQ(NULL, utf8valid("ab")); } + +UTEST(utf8valid, empty) { ASSERT_EQ(NULL, utf8valid("")); } + +UTEST(utf8nvalid, a) { + char valid[3]; + + const char *invalid = valid; + const size_t invalid_size = 1; + + valid[0] = '\xc2'; + valid[1] = '\x80'; + valid[2] = '\0'; + + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, b) { + char valid[4]; + + const char *invalid = valid; + const size_t invalid_size = 2; + + valid[0] = '\xe0'; + valid[1] = '\x80'; + valid[2] = '\x80'; + valid[3] = '\0'; + + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, c) { + char valid[5]; + + const char *invalid = valid; + const size_t invalid_size = 3; + + valid[0] = '\xf0'; + valid[1] = '\x80'; + valid[2] = '\x80'; + valid[3] = '\x80'; + valid[4] = '\0'; + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, data) { ASSERT_EQ(NULL, utf8nvalid(data, 105)); } + +UTEST(utf8nvalid, ascii) { ASSERT_EQ(NULL, utf8nvalid("ab", 3)); } + +UTEST(utf8nvalid, empty) { ASSERT_EQ(NULL, utf8nvalid("", 1)); } + +UTEST(utf8ncat, ascii_cat_data) { + char cat[512] = {'\0'}; + cat[0] = 'a'; + cat[1] = '\0'; + ASSERT_EQ(2, utf8len(utf8ncat(cat, data, 2))); +} + +UTEST(utf8ncat, cat_data) { + char cat[512] = {'\0'}; + ASSERT_EQ(53, utf8len(utf8ncat(cat, data, 40000))); +} + +UTEST(utf8ncat, bad_cat) { + char cat[512] = {'\0'}; + ASSERT_EQ(cat, utf8valid(utf8ncat(cat, data, 1))); +} + +UTEST(utf8ncat, zero_n) { + char cat[512] = {'\0'}; + ASSERT_EQ(NULL, utf8valid(utf8ncat(cat, data, 0))); +} + +UTEST(utf8ncmp, lt_large) { ASSERT_LT(0, utf8ncmp(data, lt, 4000)); } + +UTEST(utf8ncmp, lt_small) { ASSERT_EQ(0, utf8ncmp(data, lt, 7)); } + +UTEST(utf8ncmp, eq_large) { ASSERT_EQ(0, utf8ncmp(data, data, 4000)); } + +UTEST(utf8ncmp, eq_small) { ASSERT_EQ(0, utf8ncmp(data, data, 7)); } + +UTEST(utf8ncmp, gt_large) { ASSERT_GT(0, utf8ncmp(data, gt, 4000)); } + +UTEST(utf8ncmp, gt_small) { ASSERT_EQ(0, utf8ncmp(data, gt, 7)); } + +UTEST(utf8ncpy, data_null_terminated) { + char cpy[512] = {'\0'}; + ASSERT_EQ('\0', *((char *)utf8ncpy(cpy, data, 106) + 105)); +} + +UTEST(utf8ncpy, data) { + char cpy[512] = {'\0'}; + ASSERT_EQ(53, utf8len(utf8ncpy(cpy, data, 105))); +} + +UTEST(utf8ncpy, check_no_buffer_overflow) { + utf8_int32_t i; + char buffer[11] = {0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}; + ASSERT_EQ(buffer, utf8ncpy(buffer, "foo", 10)); + + ASSERT_EQ('f', buffer[0]); + ASSERT_EQ('o', buffer[1]); + ASSERT_EQ('o', buffer[2]); + + for (i = 3; 10 != i; i++) { + ASSERT_EQ(0, buffer[i]); + } + + ASSERT_EQ((char)0xdd, buffer[10]); +} + +UTEST(utf8ncpy, check_no_n_overflow) { + char buffer[4] = {1, 2, 3, 4}; + ASSERT_EQ(buffer, utf8ncpy(buffer, "foo", 2)); + + ASSERT_EQ('f', buffer[0]); + ASSERT_EQ('o', buffer[1]); + ASSERT_EQ(3, buffer[2]); + ASSERT_EQ(4, buffer[3]); +} + +UTEST(utf8ncpy, truncated_copy_valid) { + char cpy1[32] = {'\0'}; + char cpy2[3] = {'\0'}; + char cpy3[1] = {'\0'}; + + utf8ncpy(cpy1, data, 32); + ASSERT_EQ(NULL, utf8valid(cpy1)); + + utf8ncpy(cpy2, data, 3); + ASSERT_EQ(NULL, utf8valid(cpy2)); + + utf8ncpy(cpy3, data, 1); + ASSERT_EQ(NULL, utf8valid(cpy3)); +} + +UTEST(utf8ncpy, truncated_copy_null_terminated) { + char cpy1[32] = {'\0'}; + char cpy2[2] = {'\0'}; + char cpy3[3] = {'\0'}; + + utf8ncpy(cpy1, data, 32); + ASSERT_EQ('\0', cpy1[31]); + + utf8ncpy(cpy2, data, 2); + ASSERT_EQ('\0', cpy2[0]); + + utf8ncpy(cpy3, data, 3); + ASSERT_EQ('\0', cpy3[2]); +} + +UTEST(utf8pbrk, pbrk) { ASSERT_EQ(data + 8, utf8pbrk(data, pbrk)); } + +UTEST(utf8pbrk, data) { ASSERT_EQ(data, utf8pbrk(data, data)); } + +UTEST(utf8casecmp, ascii) { ASSERT_EQ(0, utf8casecmp(ascii1, ascii2)); } +UTEST(utf8casecmp, latin_upvslow) { + ASSERT_EQ(0, utf8casecmp(lowersStr, uppersStr)); +} +UTEST(utf8casecmp, latin_lowvsup) { + ASSERT_EQ(0, utf8casecmp(uppersStr, lowersStr)); +} + +UTEST(utf8casecmp, allascii) { + ASSERT_EQ(0, utf8casecmp(allascii1, allascii2)); +} + +UTEST(utf8casecmp, data_lt) { ASSERT_LT(0, utf8casecmp(data, lt)); } + +UTEST(utf8casecmp, data_eq) { ASSERT_EQ(0, utf8casecmp(data, data)); } + +UTEST(utf8casecmp, data_gt) { ASSERT_GT(0, utf8casecmp(data, gt)); } + +UTEST(utf8ncasecmp, lt_large) { ASSERT_LT(0, utf8ncasecmp(data, lt, 4000)); } + +UTEST(utf8ncasecmp, lt_small) { ASSERT_EQ(0, utf8ncasecmp(data, lt, 7)); } + +UTEST(utf8ncasecmp, eq_large) { ASSERT_EQ(0, utf8ncasecmp(data, data, 4000)); } + +UTEST(utf8ncasecmp, eq_small) { ASSERT_EQ(0, utf8ncasecmp(data, data, 7)); } + +UTEST(utf8ncasecmp, gt_large) { ASSERT_GT(0, utf8ncasecmp(data, gt, 4000)); } + +UTEST(utf8ncasecmp, gt_small) { ASSERT_EQ(0, utf8ncasecmp(data, gt, 7)); } + +UTEST(utf8ncasecmp, ascii) { ASSERT_EQ(0, utf8ncasecmp(ascii1, ascii2, 4)); } +UTEST(utf8ncasecmp, latin_upvslow) { + ASSERT_EQ(0, utf8ncasecmp(lowersStr, uppersStr, 120)); +} +UTEST(utf8ncasecmp, latin_lowvsup) { + ASSERT_EQ(0, utf8ncasecmp(uppersStr, lowersStr, 120)); +} + +UTEST(utf8ncasecmp, basic_ascii) { + ASSERT_EQ(-15, utf8ncasecmp(".gdoc", ".GSHeeT", 5)); + ASSERT_EQ(-4, utf8ncasecmp(".gsheet", ".gSLiDe", 7)); + +#ifndef _MSC_VER + ASSERT_EQ(strcasecmp(".gdoc", ".GSHeeT"), + utf8ncasecmp(".gdoc", ".GSHeeT", 5)); + ASSERT_EQ(strcasecmp(".gsheet", ".gSLiDe"), + utf8ncasecmp(".gsheet", ".gSLiDe", 7)); +#endif +} + +UTEST(utf8ncasecmp, latin_extended_a) { + ASSERT_EQ(96, utf8ncasecmp("Camón Romasan", "camu", 4)); +} + +UTEST(utf8codepoint, data) { + utf8_int32_t codepoint; + void *v; + size_t expected_length = utf8len(data) - 1; + for (v = utf8codepoint(data, &codepoint); codepoint; + v = utf8codepoint(v, &codepoint)) { + ASSERT_EQ(expected_length, utf8len(v)); + expected_length -= 1; + } +} + +UTEST(utf8codepointcalcsize, data) { + const char *v; + // No -1 here since we start at the beginning + size_t expected_length = utf8len(data); + for (v = data; *v; v += utf8codepointcalcsize(v)) { + ASSERT_EQ(expected_length, utf8len(v)); + expected_length -= 1; + } +} + +UTEST(utf8codepointsize, size_1) { ASSERT_EQ(1, utf8codepointsize('A')); } + +UTEST(utf8codepointsize, size_4) { ASSERT_EQ(4, utf8codepointsize(0x20C78)); } + +UTEST(utf8catcodepoint, data) { + char buffer[129]; + char *p = buffer; + long cp; + int i; + memset(buffer, 0, 129); + for (i = 0; i < 128; i++) { + cp = (i % 2 == 0 ? 'A' : 0x20C78); + p = utf8catcodepoint(p, cp, 128 - (p - buffer)); + if (!p) { + break; + } + } + ASSERT_EQ(51, utf8len(buffer)); +} + +UTEST(utf8islower, upper) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(0, utf8islower(lowupPairs[i].upper)); + } +} + +UTEST(utf8islower, lower) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(1, utf8islower(lowupPairs[i].lower)); + } +} + +UTEST(utf8isupper, upper) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(1, utf8isupper(lowupPairs[i].upper)); + } +} + +UTEST(utf8isupper, lower) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(0, utf8isupper(lowupPairs[i].lower)); + } +} + +UTEST(utf8lwr, ascii) { + size_t sz; + char *str; + sz = strlen(ascii1); + str = (char *)malloc(sz + 1); + memcpy(str, ascii1, sz + 1); + utf8lwr(str); + ASSERT_EQ(0, strcmp(str, "i like goats yarhar.")); + free(str); +} + +UTEST(utf8lwr, latin_lower) { + size_t sz; + void *str; + sz = utf8size(lowersStr); + str = malloc(sz); + memcpy(str, lowersStr, sz); + utf8lwr(str); + ASSERT_EQ(0, utf8cmp(str, lowersStr)); + free(str); +} + +UTEST(utf8lwr, latin_upper) { + size_t sz; + void *str; + sz = utf8size(uppersStr); + str = malloc(sz); + memcpy(str, uppersStr, sz); + utf8lwr(str); + ASSERT_EQ(0, utf8cmp(str, lowersStr)); + free(str); +} + +UTEST(utf8upr, ascii) { + size_t sz; + char *str; + sz = strlen(ascii1); + str = (char *)malloc(sz + 1); + memcpy(str, ascii1, sz + 1); + utf8upr(str); + ASSERT_EQ(0, strcmp(str, "I LIKE GOATS YARHAR.")); + free(str); +} + +UTEST(utf8upr, latin_lower) { + size_t sz; + void *str; + sz = utf8size(lowersStr); + str = malloc(sz); + memcpy(str, lowersStr, sz); + utf8upr(str); + ASSERT_EQ(0, utf8cmp(str, uppersStr)); + free(str); +} + +UTEST(utf8upr, latin_upper) { + size_t sz; + void *str; + sz = utf8size(uppersStr); + str = malloc(sz); + memcpy(str, uppersStr, sz); + utf8upr(str); + ASSERT_EQ(0, utf8cmp(str, uppersStr)); + free(str); +} + +UTEST(utf8casecmp, basic_ascii) { + ASSERT_EQ(-15, utf8casecmp(".gdoc", ".GSHeeT")); + ASSERT_EQ(-4, utf8casecmp(".gsheet", ".gSLiDe")); + +#ifndef _MSC_VER + ASSERT_EQ(strcasecmp(".gdoc", ".GSHeeT"), utf8casecmp(".gdoc", ".GSHeeT")); + ASSERT_EQ(strcasecmp(".gsheet", ".gSLiDe"), + utf8casecmp(".gsheet", ".gSLiDe")); +#endif +} + +UTEST(utf8lwr, greek_capital_theta) { + const char ref[] = {'\xce', '\xb8', '\xce', '\xb8', '\xce', + '\xb8', '\xcf', '\x91', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + utf8lwr(str); + + ASSERT_EQ(0, utf8cmp(str, ref)); +} + +UTEST(utf8upr, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + utf8upr(str); + + ASSERT_EQ(0, utf8cmp(str, ref)); +} + +UTEST(utf8casecmp, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + ASSERT_EQ(0, utf8casecmp(ref, str)); +} + +UTEST(utf8ncasecmp, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + ASSERT_EQ(0, utf8ncasecmp(ref, str, 8)); +} + +UTEST(utf8rcodepoint, ascii) { + utf8_int32_t codepoint; + + ASSERT_EQ(ascii1, utf8rcodepoint(ascii1 + 1, &codepoint)); + + ASSERT_EQ(ascii1[1], codepoint); +} + +UTEST(utf8rcodepoint, latin) { + utf8_int32_t codepoint; + + ASSERT_EQ(data, utf8rcodepoint(data + 2, &codepoint)); + + ASSERT_EQ(0x3B1, codepoint); +} + +UTEST(utf8makevalid, a) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf0'; + invalid[1] = '\x8f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xef'); +} + +UTEST(utf8makevalid, b) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, c) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, d) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, e) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xe0'; + invalid[1] = '\x9f'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xdf'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\0'); +} + +UTEST(utf8makevalid, f) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, g) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, h) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xc1'; + invalid[1] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\x7f'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, i) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '\0'); +} + +UTEST(utf8makevalid, j) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\x80'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, k) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf8'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, l) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xf1'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\xbf'); + ASSERT_EQ(invalid[3], '\xbf'); + ASSERT_EQ(invalid[4], '?'); + ASSERT_EQ(invalid[5], '\0'); +} + +UTEST(utf8makevalid, m) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xef'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\xbf'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, n) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xdf'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, invalid_replacement) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + + ASSERT_NE(0, utf8makevalid(invalid, 0x80)); +} + +UTEST(utf8nvalid, exactly_2_bytes) { + const char terminated[] = "\xc2\xa3"; + ASSERT_EQ(utf8nvalid(terminated, 2), NULL); +} + +UTEST(utf8nvalid, exactly_3_bytes) { + const char terminated[] = "\xe1\xbd\xb6"; + ASSERT_EQ(utf8nvalid(terminated, 3), NULL); +} + +UTEST(utf8nvalid, exactly_4_bytes) { + const char terminated[] = "\xf0\x90\x8d\x88"; + ASSERT_EQ(utf8nvalid(terminated, 4), NULL); +} + +UTEST_MAIN(); diff --git a/include/utf8/test/no_malloc.c b/include/utf8/test/no_malloc.c new file mode 100644 index 0000000..b0d4a05 --- /dev/null +++ b/include/utf8/test/no_malloc.c @@ -0,0 +1,64 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +// include the unit testing framework +#include "utest.h" + +// include the header we are testing +#define UTF8_NO_STD_MALLOC +#include "utf8.h" + +UTEST(no_malloc_utf8dup, ascii) { + void *const dup = utf8dup("1234567890"); + ASSERT_FALSE(dup); +} + +UTEST(no_malloc_utf8ndup, ascii) { + void *const dup = utf8ndup("1234567890", 4); + ASSERT_FALSE(dup); +} + +static utf8_int8_t *allocate_from_buffer(utf8_int8_t *user_data, size_t n) { + return user_data; +} + +UTEST(no_malloc_utf8dup_ex, ascii) { + char user_data[1024]; + void *const dup = utf8dup_ex("1234567890", allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(10, utf8len(dup)); +} + +UTEST(no_malloc_utf8ndup_ex, ascii) { + char user_data[1024]; + void *const dup = + utf8ndup_ex("1234567890", 4, allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(4, utf8len(dup)); +} + +UTEST_MAIN(); diff --git a/include/utf8/test/test.c b/include/utf8/test/test.c new file mode 100644 index 0000000..397bcdc --- /dev/null +++ b/include/utf8/test/test.c @@ -0,0 +1,33 @@ +/* This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include "utf8.h" + +int main(const int argc, const char *const argv[]) { + (void)argc; + (void)argv; + return 0; +} diff --git a/include/utf8/test/test.cpp b/include/utf8/test/test.cpp new file mode 100644 index 0000000..81900b1 --- /dev/null +++ b/include/utf8/test/test.cpp @@ -0,0 +1,78 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +#include "utf8.h" + +// We don't care about the results. We only want to check compilation + +#if defined(__clang__) +#pragma clang diagnostic push + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +#if defined(__cplusplus) && __cplusplus >= 201402L +constexpr void test() { + constexpr utf8_int8_t in_str[20]{}; + constexpr utf8_int32_t in_chr{}; + utf8_int32_t out_chr{}; + + utf8codepoint(in_str, &out_chr); + utf8rcodepoint(in_str + 1, &out_chr); + static_assert(utf8chr(in_str, utf8_int32_t{}), "utf8 constexpr fail"); + static_assert(utf8cmp(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8cspn(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8len(in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8nlen(in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8ncmp(in_str, in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8pbrk(in_str, in_str) == nullptr, "utf8 constexpr fail"); + static_assert(utf8rchr(in_str, 1) == nullptr, "utf8 constexpr fail"); + static_assert(utf8spn(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8str(in_str, in_str), "utf8 constexpr fail"); + static_assert(utf8casecmp(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8ncasecmp(in_str, in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8casestr(in_str, in_str), "utf8 constexpr fail"); + static_assert(utf8size(in_str), "utf8 constexpr fail"); + static_assert(utf8size_lazy(in_str) == 0, "utf8 constexpr faillazy;"); + static_assert(utf8nsize_lazy(in_str, 1) == 0, "utf8 constexpr faillazy;"); + static_assert(utf8valid(in_str) == nullptr, "utf8 constexpr fail"); + static_assert(utf8nvalid(in_str, 1) == nullptr, "utf8 constexpr fail"); + static_assert(utf8codepointsize(in_chr), "utf8 constexpr fail"); + static_assert(utf8isupper(in_chr) == false, "utf8 constexpr fail"); + static_assert(utf8islower(in_chr) == false, "utf8 constexpr fail"); + static_assert(utf8lwrcodepoint(in_chr) == in_chr, "utf8 constexpr fail"); + static_assert(utf8uprcodepoint(in_chr) == in_chr, "utf8 constexpr fail"); +} +#else +static void test() {} +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +int main() { test(); } diff --git a/include/utf8/test/utest.h b/include/utf8/test/utest.h new file mode 100644 index 0000000..8767600 --- /dev/null +++ b/include/utf8/test/utest.h @@ -0,0 +1,1668 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/utest.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_UTEST_H_INCLUDED +#define SHEREDOM_UTEST_H_INCLUDED + +#ifdef _MSC_VER +/* + Disable warning about not inlining 'inline' functions. +*/ +#pragma warning(disable : 4710) + +/* + Disable warning about inlining functions that are not marked 'inline'. +*/ +#pragma warning(disable : 4711) + +/* + Disable warning for alignment padding added +*/ +#pragma warning(disable : 4820) + +#if _MSC_VER > 1900 +/* + Disable warning about preprocessor macros not being defined in MSVC headers. +*/ +#pragma warning(disable : 4668) + +/* + Disable warning about no function prototype given in MSVC headers. +*/ +#pragma warning(disable : 4255) + +/* + Disable warning about pointer or reference to potentially throwing function. +*/ +#pragma warning(disable : 5039) + +/* + Disable warning about macro expansion producing 'defined' has undefined + behavior. +*/ +#pragma warning(disable : 5105) +#endif + +#if _MSC_VER > 1930 +/* + Disable warning about 'const' variable is not used. +*/ +#pragma warning(disable : 5264) +#endif + +#pragma warning(push, 1) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +typedef __int64 utest_int64_t; +typedef unsigned __int64 utest_uint64_t; +typedef unsigned __int32 utest_uint32_t; +#else +#include +typedef int64_t utest_int64_t; +typedef uint64_t utest_uint64_t; +typedef uint32_t utest_uint32_t; +#endif + +#include +#include +#include +#include +#include + +#if defined(__cplusplus) +#if defined(_MSC_VER) && !defined(_CPPUNWIND) +/* We're on MSVC and the compiler is compiling without exception support! */ +#elif !defined(_MSC_VER) && !defined(__EXCEPTIONS) +/* We're on a GCC/Clang compiler that doesn't have exception support! */ +#else +#define UTEST_HAS_EXCEPTIONS 1 +#endif +#endif + +#if defined(UTEST_HAS_EXCEPTIONS) +#include +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__cplusplus) +#define UTEST_C_FUNC extern "C" +#else +#define UTEST_C_FUNC +#endif + +#define UTEST_TEST_PASSED (0) +#define UTEST_TEST_FAILURE (1) +#define UTEST_TEST_SKIPPED (2) + +#if defined(__TINYC__) +#define UTEST_ATTRIBUTE(a) __attribute((a)) +#else +#define UTEST_ATTRIBUTE(a) __attribute__((a)) +#endif + +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) + +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#endif + +#if defined(_WINDOWS_) || defined(_WINDOWS_H) +typedef LARGE_INTEGER utest_large_integer; +#else +// use old QueryPerformanceCounter definitions (not sure is this needed in some +// edge cases or not) on Win7 with VS2015 these extern declaration cause "second +// C linkage of overloaded function not allowed" error +typedef union { + struct { + unsigned long LowPart; + long HighPart; + } DUMMYSTRUCTNAME; + struct { + unsigned long LowPart; + long HighPart; + } u; + utest_int64_t QuadPart; +} utest_large_integer; + +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceCounter( + utest_large_integer *); +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceFrequency( + utest_large_integer *); + +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif +#endif + +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) +/* + slightly obscure include here - we need to include glibc's features.h, but + we don't want to just include a header that might not be defined for other + c libraries like musl. Instead we include limits.h, which we know on all + glibc distributions includes features.h +*/ +#include + +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) +#include + +#if ((2 < __GLIBC__) || ((2 == __GLIBC__) && (17 <= __GLIBC_MINOR__))) +/* glibc is version 2.17 or above, so we can just use clock_gettime */ +#define UTEST_USE_CLOCKGETTIME +#else +#include +#include +#endif +#else // Other libc implementations +#include +#define UTEST_USE_CLOCKGETTIME +#endif + +#elif defined(__APPLE__) +#include +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define UTEST_PRId64 "I64d" +#define UTEST_PRIu64 "I64u" +#else +#include + +#define UTEST_PRId64 PRId64 +#define UTEST_PRIu64 PRIu64 +#endif + +#if defined(__cplusplus) +#define UTEST_INLINE inline + +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + +#define UTEST_INITIALIZER(f) \ + struct f##_cpp_struct { \ + f##_cpp_struct(); \ + }; \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS static f##_cpp_struct \ + f##_cpp_global UTEST_INITIALIZER_END_DISABLE_WARNINGS; \ + f##_cpp_struct::f##_cpp_struct() +#elif defined(_MSC_VER) +#define UTEST_INLINE __forceinline + +#if defined(_WIN64) +#define UTEST_SYMBOL_PREFIX +#else +#define UTEST_SYMBOL_PREFIX "_" +#endif + +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wmissing-variable-declarations\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + +#pragma section(".CRT$XCU", read) +#define UTEST_INITIALIZER(f) \ + static void __cdecl f(void); \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")) \ + UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * \ + f##_)(void) = f; \ + UTEST_INITIALIZER_END_DISABLE_WARNINGS \ + static void __cdecl f(void) +#else +#if defined(__linux__) +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif +#endif + +#define __STDC_FORMAT_MACROS 1 + +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic pop +#endif +#endif +#endif + +#define UTEST_INLINE inline + +#define UTEST_INITIALIZER(f) \ + static void f(void) UTEST_ATTRIBUTE(constructor); \ + static void f(void) +#endif + +#if defined(__cplusplus) +#define UTEST_CAST(type, x) static_cast(x) +#define UTEST_PTR_CAST(type, x) reinterpret_cast(x) +#define UTEST_EXTERN extern "C" +#define UTEST_NULL NULL +#else +#define UTEST_CAST(type, x) ((type)(x)) +#define UTEST_PTR_CAST(type, x) ((type)(x)) +#define UTEST_EXTERN extern +#define UTEST_NULL 0 +#endif + +#ifdef _MSC_VER +/* + io.h contains definitions for some structures with natural padding. This is + uninteresting, but for some reason MSVC's behaviour is to warn about + including this system header. That *is* interesting +*/ +#pragma warning(disable : 4820) +#pragma warning(push, 1) +#include +#pragma warning(pop) +#define UTEST_COLOUR_OUTPUT() (_isatty(_fileno(stdout))) +#else +#if defined(__EMSCRIPTEN__) +#include +#define UTEST_COLOUR_OUTPUT() false +#else +#include +#define UTEST_COLOUR_OUTPUT() (isatty(STDOUT_FILENO)) +#endif +#endif + +static UTEST_INLINE void *utest_realloc(void *const pointer, size_t new_size) { + void *const new_pointer = realloc(pointer, new_size); + + if (UTEST_NULL == new_pointer) { + free(new_pointer); + } + + return new_pointer; +} + +static UTEST_INLINE utest_int64_t utest_ns(void) { +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) + utest_large_integer counter; + utest_large_integer frequency; + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&frequency); + return UTEST_CAST(utest_int64_t, + (counter.QuadPart * 1000000000) / frequency.QuadPart); +#elif defined(__linux__) && defined(__STRICT_ANSI__) + return UTEST_CAST(utest_int64_t, clock()) * 1000000000 / CLOCKS_PER_SEC; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) + struct timespec ts; +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(__HAIKU__) + timespec_get(&ts, TIME_UTC); +#else + const clockid_t cid = CLOCK_REALTIME; +#if defined(UTEST_USE_CLOCKGETTIME) + clock_gettime(cid, &ts); +#else + syscall(SYS_clock_gettime, cid, &ts); +#endif +#endif + return UTEST_CAST(utest_int64_t, ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; +#elif __APPLE__ + return UTEST_CAST(utest_int64_t, clock_gettime_nsec_np(CLOCK_UPTIME_RAW)); +#elif __EMSCRIPTEN__ + return emscripten_performance_now() * 1000000.0; +#else +#error Unsupported platform! +#endif +} + +typedef void (*utest_testcase_t)(int *, size_t); + +struct utest_test_state_s { + utest_testcase_t func; + size_t index; + char *name; +}; + +struct utest_state_s { + struct utest_test_state_s *tests; + size_t tests_length; + FILE *output; +}; + +/* extern to the global state utest needs to execute */ +UTEST_EXTERN struct utest_state_s utest_state; + +#if defined(_MSC_VER) +#define UTEST_WEAK __forceinline +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define UTEST_WEAK static UTEST_ATTRIBUTE(used) +#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__) +#define UTEST_WEAK UTEST_ATTRIBUTE(weak) +#else +#error Non clang, non gcc, non MSVC, non tcc compiler found! +#endif + +#if defined(_MSC_VER) +#define UTEST_UNUSED +#else +#define UTEST_UNUSED UTEST_ATTRIBUTE(unused) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#define UTEST_PRINTF(...) \ + if (utest_state.output) { \ + fprintf(utest_state.output, __VA_ARGS__); \ + } \ + printf(__VA_ARGS__) +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#ifdef _MSC_VER +#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) +#else +#define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(__cplusplus) +/* if we are using c++ we can use overloaded methods (its in the language) */ +#define UTEST_OVERLOADABLE +#elif defined(__clang__) +/* otherwise, if we are using clang with c - use the overloadable attribute */ +#define UTEST_OVERLOADABLE UTEST_ATTRIBUTE(overloadable) +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#include + +template ::value> +struct utest_type_deducer final { + static void _(const T t); +}; + +template <> struct utest_type_deducer { + static void _(const signed char c) { + UTEST_PRINTF("%d", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned char c) { + UTEST_PRINTF("%u", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const short s) { UTEST_PRINTF("%d", static_cast(s)); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned short s) { + UTEST_PRINTF("%u", static_cast(s)); + } +}; + +template <> struct utest_type_deducer { + static void _(const float f) { UTEST_PRINTF("%f", static_cast(f)); } +}; + +template <> struct utest_type_deducer { + static void _(const double d) { UTEST_PRINTF("%f", d); } +}; + +template <> struct utest_type_deducer { + static void _(const long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else + UTEST_PRINTF("%Lf", d); +#endif + } +}; + +template <> struct utest_type_deducer { + static void _(const int i) { UTEST_PRINTF("%d", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned int i) { UTEST_PRINTF("%u", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long i) { UTEST_PRINTF("%ld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long i) { UTEST_PRINTF("%lu", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long long i) { UTEST_PRINTF("%lld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long long i) { UTEST_PRINTF("%llu", i); } +}; + +template struct utest_type_deducer { + static void _(const T *t) { + UTEST_PRINTF("%p", static_cast(const_cast(t))); + } +}; + +template struct utest_type_deducer { + static void _(T *t) { UTEST_PRINTF("%p", static_cast(t)); } +}; + +template struct utest_type_deducer { + static void _(const T t) { + UTEST_PRINTF("%llu", static_cast(t)); + } +}; + +template +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const T t) { + utest_type_deducer::_(t); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined(UTEST_OVERLOADABLE) + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c) { + UTEST_PRINTF("%d", UTEST_CAST(int, c)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c) { + UTEST_PRINTF("%u", UTEST_CAST(unsigned int, c)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f) { + UTEST_PRINTF("%f", UTEST_CAST(double, f)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d) { + UTEST_PRINTF("%f", d); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else + UTEST_PRINTF("%Lf", d); +#endif +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i) { + UTEST_PRINTF("%d", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i) { + UTEST_PRINTF("%u", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i) { + UTEST_PRINTF("%ld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i) { + UTEST_PRINTF("%lu", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p) { + UTEST_PRINTF("%p", p); +} + +/* + long long is a c++11 extension +*/ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || \ + defined(__cplusplus) && (__cplusplus >= 201103L) || \ + (defined(__MINGW32__) || defined(__MINGW64__)) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i) { + UTEST_PRINTF("%lld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void +utest_type_printer(long long unsigned int i) { + UTEST_PRINTF("%llu", i); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !(defined(__MINGW32__) || defined(__MINGW64__)) || \ + defined(__TINYC__) +#define utest_type_printer(val) \ + UTEST_PRINTF(_Generic((val), signed char \ + : "%d", unsigned char \ + : "%u", short \ + : "%d", unsigned short \ + : "%u", int \ + : "%d", long \ + : "%ld", long long \ + : "%lld", unsigned \ + : "%u", unsigned long \ + : "%lu", unsigned long long \ + : "%llu", float \ + : "%f", double \ + : "%f", long double \ + : "%Lf", default \ + : _Generic((val - val), ptrdiff_t \ + : "%p", default \ + : "undef")), \ + (val)) +#else +/* + we don't have the ability to print the values we got, so we create a macro + to tell our users we can't do anything fancy +*/ +#define utest_type_printer(...) UTEST_PRINTF("undef") +#endif + +#if defined(_MSC_VER) +#define UTEST_SURPRESS_WARNING_BEGIN \ + __pragma(warning(push)) __pragma(warning(disable : 4127)) \ + __pragma(warning(disable : 4571)) __pragma(warning(disable : 4130)) +#define UTEST_SURPRESS_WARNING_END __pragma(warning(pop)) +#else +#define UTEST_SURPRESS_WARNING_BEGIN +#define UTEST_SURPRESS_WARNING_END +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define UTEST_AUTO(x) auto +#elif !defined(__cplusplus) + +#if defined(__clang__) +/* clang-format off */ +/* had to disable clang-format here because it malforms the pragmas */ +#define UTEST_AUTO(x) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wgnu-auto-type\"") __auto_type \ + _Pragma("clang diagnostic pop") +/* clang-format on */ +#else +#define UTEST_AUTO(x) __typeof__(x + 0) +#endif + +#else +#define UTEST_AUTO(x) typeof(x + 0) +#endif + +#if defined(__clang__) +#define UTEST_STRNCMP(x, y, size) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdisabled-macro-expansion\"") \ + strncmp(x, y, size) _Pragma("clang diagnostic pop") +#else +#define UTEST_STRNCMP(x, y, size) strncmp(x, y, size) +#endif + +#if defined(_MSC_VER) +#define UTEST_STRNCPY(x, y, size) strcpy_s(x, size, y) +#elif !defined(__clang__) && defined(__GNUC__) +static UTEST_INLINE char * +utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + return strncpy(dst, src, size); +#pragma GCC diagnostic pop +} + +#define UTEST_STRNCPY(x, y, size) utest_strncpy_gcc(x, y, size) +#else +#define UTEST_STRNCPY(x, y, size) strncpy(x, y, size) +#endif + +#define UTEST_SKIP(msg) \ + do { \ + UTEST_PRINTF(" Skipped : '%s'\n", (msg)); \ + *utest_result = UTEST_TEST_SKIPPED; \ + return; \ + } while (0) + +#if defined(__clang__) +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat-pedantic\"") \ + _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \ + UTEST_AUTO(x) xEval = (x); \ + UTEST_AUTO(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + _Pragma("clang diagnostic pop") \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF(")\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#elif defined(__GNUC__) || defined(__TINYC__) +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + UTEST_AUTO(x) xEval = (x); \ + UTEST_AUTO(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF(")\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#else +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + if (!((x)cond(y))) { \ + UTEST_PRINTF("%s:%i: Failure (Expected " #cond " Actual)", __FILE__, \ + __LINE__); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s", msg); \ + } \ + UTEST_PRINTF("\n"); \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#endif + +#define EXPECT_EQ(x, y) UTEST_COND(x, y, ==, "", 0) +#define EXPECT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 0) +#define ASSERT_EQ(x, y) UTEST_COND(x, y, ==, "", 1) +#define ASSERT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 1) + +#define EXPECT_NE(x, y) UTEST_COND(x, y, !=, "", 0) +#define EXPECT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 0) +#define ASSERT_NE(x, y) UTEST_COND(x, y, !=, "", 1) +#define ASSERT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 1) + +#define EXPECT_LT(x, y) UTEST_COND(x, y, <, "", 0) +#define EXPECT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 0) +#define ASSERT_LT(x, y) UTEST_COND(x, y, <, "", 1) +#define ASSERT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 1) + +#define EXPECT_LE(x, y) UTEST_COND(x, y, <=, "", 0) +#define EXPECT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 0) +#define ASSERT_LE(x, y) UTEST_COND(x, y, <=, "", 1) +#define ASSERT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 1) + +#define EXPECT_GT(x, y) UTEST_COND(x, y, >, "", 0) +#define EXPECT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 0) +#define ASSERT_GT(x, y) UTEST_COND(x, y, >, "", 1) +#define ASSERT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 1) + +#define EXPECT_GE(x, y) UTEST_COND(x, y, >=, "", 0) +#define EXPECT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 0) +#define ASSERT_GE(x, y) UTEST_COND(x, y, >=, "", 1) +#define ASSERT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 1) + +#define UTEST_TRUE(x, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const int xEval = !!(x); \ + if (!(xEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : true\n"); \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_TRUE(x) UTEST_TRUE(x, "", 0) +#define EXPECT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 0) +#define ASSERT_TRUE(x) UTEST_TRUE(x, "", 1) +#define ASSERT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 1) + +#define UTEST_FALSE(x, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const int xEval = !!(x); \ + if (xEval) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : false\n"); \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_FALSE(x) UTEST_FALSE(x, "", 0) +#define EXPECT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 0) +#define ASSERT_FALSE(x) UTEST_FALSE(x, "", 1) +#define ASSERT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 1) + +#define UTEST_STREQ(x, y, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STREQ(x, y) UTEST_STREQ(x, y, "", 0) +#define EXPECT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 0) +#define ASSERT_STREQ(x, y) UTEST_STREQ(x, y, "", 1) +#define ASSERT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 1) + +#define UTEST_STRNE(x, y, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNE(x, y) UTEST_STRNE(x, y, "", 0) +#define EXPECT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 0) +#define ASSERT_STRNE(x, y) UTEST_STRNE(x, y, "", 1) +#define ASSERT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 1) + +#define UTEST_STRNEQ(x, y, n, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 0) +#define EXPECT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 0) +#define ASSERT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 1) +#define ASSERT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 1) + +#define UTEST_STRNNE(x, y, n, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 0) +#define EXPECT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 0) +#define ASSERT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 1) +#define ASSERT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 1) + +#define UTEST_NEAR(x, y, epsilon, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const double diff = \ + utest_fabs(UTEST_CAST(double, x) - UTEST_CAST(double, y)); \ + if (diff > UTEST_CAST(double, epsilon) || utest_isnan(diff)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %f\n", UTEST_CAST(double, x)); \ + UTEST_PRINTF(" Actual : %f\n", UTEST_CAST(double, y)); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 0) +#define EXPECT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 0) +#define ASSERT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 1) +#define ASSERT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 1) + +#if defined(UTEST_HAS_EXCEPTIONS) +#define UTEST_EXCEPTION(x, exception_type, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + int exception_caught = 0; \ + try { \ + x; \ + } catch (const exception_type &) { \ + exception_caught = 1; \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 0) +#define EXPECT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 0) +#define ASSERT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 1) +#define ASSERT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 1) + +#define UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, \ + msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + int exception_caught = 0; \ + char *message_caught = UTEST_NULL; \ + try { \ + x; \ + } catch (const exception_type &e) { \ + const char *const what = e.what(); \ + exception_caught = 1; \ + if (0 != \ + UTEST_STRNCMP(what, exception_message, strlen(exception_message))) { \ + const size_t message_size = strlen(what) + 1; \ + message_caught = UTEST_PTR_CAST(char *, malloc(message_size)); \ + UTEST_STRNCPY(message_caught, what, message_size); \ + } \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } else if (UTEST_NULL != message_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception with message %s\n", \ + #exception_type, exception_message); \ + UTEST_PRINTF(" Actual message : %s\n", message_caught); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + free(message_caught); \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 0) +#define EXPECT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 0) +#define ASSERT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 1) +#define ASSERT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 1) +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunsafe-buffer-usage\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("clang diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif +#elif defined(__GNUC__) && __GNUC__ >= 8 && defined(__cplusplus) +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wclass-memaccess\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("GCC diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif + +#define UTEST(SET, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##SET##_##NAME(int *utest_result); \ + static void utest_##SET##_##NAME(int *utest_result, size_t utest_index) { \ + (void)utest_index; \ + utest_run_##SET##_##NAME(utest_result); \ + } \ + UTEST_INITIALIZER(utest_register_##SET##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #SET "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_##SET##_##NAME; \ + utest_state.tests[index].name = name; \ + utest_state.tests[index].index = 0; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##SET##_##NAME(int *utest_result) + +#define UTEST_F_SETUP(FIXTURE) \ + static void utest_f_setup_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F_TEARDOWN(FIXTURE) \ + static void utest_f_teardown_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F(FIXTURE, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_f_setup_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_f_teardown_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_run_##FIXTURE##_##NAME(int *, struct FIXTURE *); \ + static void utest_f_##FIXTURE##_##NAME(int *utest_result, \ + size_t utest_index) { \ + struct FIXTURE fixture; \ + (void)utest_index; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_f_setup_##FIXTURE(utest_result, &fixture); \ + if (UTEST_TEST_PASSED != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME(utest_result, &fixture); \ + utest_f_teardown_##FIXTURE(utest_result, &fixture); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ + utest_state.tests[index].name = name; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##FIXTURE##_##NAME(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_I_SETUP(FIXTURE) \ + static void utest_i_setup_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I_TEARDOWN(FIXTURE) \ + static void utest_i_teardown_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I(FIXTURE, NAME, INDEX) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##FIXTURE##_##NAME##_##INDEX(int *, struct FIXTURE *); \ + static void utest_i_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + size_t index) { \ + struct FIXTURE fixture; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_i_setup_##FIXTURE(utest_result, &fixture, index); \ + if (UTEST_TEST_PASSED != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME##_##INDEX(utest_result, &fixture); \ + utest_i_teardown_##FIXTURE(utest_result, &fixture, index); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME##_##INDEX) { \ + size_t i; \ + utest_uint64_t iUp; \ + for (i = 0; i < (INDEX); i++) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 32; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ + utest_state.tests[index].index = i; \ + utest_state.tests[index].name = name; \ + iUp = UTEST_CAST(utest_uint64_t, i); \ + UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ + } else if (name) { \ + free(name); \ + } \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +UTEST_WEAK +double utest_fabs(double d); +UTEST_WEAK +double utest_fabs(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.d; +} + +UTEST_WEAK +int utest_isnan(double d); +UTEST_WEAK +int utest_isnan(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.u > 0x7ff0000000000000u; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +UTEST_WEAK +int utest_should_filter_test(const char *filter, const char *testcase); +UTEST_WEAK int utest_should_filter_test(const char *filter, + const char *testcase) { + if (filter) { + const char *filter_cur = filter; + const char *testcase_cur = testcase; + const char *filter_wildcard = UTEST_NULL; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* store the position of the wildcard */ + filter_wildcard = filter_cur; + + /* skip the wildcard character */ + filter_cur++; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* + we found another wildcard (filter is something like *foo*) so we + exit the current loop, and return to the parent loop to handle + the wildcard case + */ + break; + } else if (*filter_cur != *testcase_cur) { + /* otherwise our filter didn't match, so reset it */ + filter_cur = filter_wildcard; + } + + /* move testcase along */ + testcase_cur++; + + /* move filter along */ + filter_cur++; + } + + if (('\0' == *filter_cur) && ('\0' == *testcase_cur)) { + return 0; + } + + /* if the testcase has been exhausted, we don't have a match! */ + if ('\0' == *testcase_cur) { + return 1; + } + } else { + if (*testcase_cur != *filter_cur) { + /* test case doesn't match filter */ + return 1; + } else { + /* move our filter and testcase forward */ + testcase_cur++; + filter_cur++; + } + } + } + + if (('\0' != *filter_cur) || + (('\0' != *testcase_cur) && + ((filter == filter_cur) || ('*' != filter_cur[-1])))) { + /* we have a mismatch! */ + return 1; + } + } + + return 0; +} + +static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) { +#ifdef _MSC_VER + FILE *file; + if (0 == fopen_s(&file, filename, mode)) { + return file; + } else { + return UTEST_NULL; + } +#else + return fopen(filename, mode); +#endif +} + +static UTEST_INLINE int utest_main(int argc, const char *const argv[]); +int utest_main(int argc, const char *const argv[]) { + utest_uint64_t failed = 0; + utest_uint64_t skipped = 0; + size_t index = 0; + size_t *failed_testcases = UTEST_NULL; + size_t failed_testcases_length = 0; + size_t *skipped_testcases = UTEST_NULL; + size_t skipped_testcases_length = 0; + const char *filter = UTEST_NULL; + utest_uint64_t ran_tests = 0; + int enable_mixed_units = 0; + int random_order = 0; + utest_uint32_t seed = 0; + + enum colours { RESET, GREEN, RED, YELLOW }; + + const int use_colours = UTEST_COLOUR_OUTPUT(); + const char *colours[] = {"\033[0m", "\033[32m", "\033[31m", "\033[33m"}; + + if (!use_colours) { + for (index = 0; index < sizeof colours / sizeof colours[0]; index++) { + colours[index] = ""; + } + } + /* loop through all arguments looking for our options */ + for (index = 1; index < UTEST_CAST(size_t, argc); index++) { + /* Informational switches */ + const char help_str[] = "--help"; + const char list_str[] = "--list-tests"; + /* Test config switches */ + const char filter_str[] = "--filter="; + const char output_str[] = "--output="; + const char enable_mixed_units_str[] = "--enable-mixed-units"; + const char random_order_str[] = "--random-order"; + const char random_order_with_seed_str[] = "--random-order="; + + if (0 == UTEST_STRNCMP(argv[index], help_str, strlen(help_str))) { + printf("utest.h - the single file unit testing solution for C/C++!\n" + "Command line Options:\n" + " --help Show this message and exit.\n" + " --filter= Filter the test cases to run (EG. " + "MyTest*.a would run MyTestCase.a but not MyTestCase.b).\n" + " --list-tests List testnames, one per line. Output " + "names can be passed to --filter.\n"); + printf(" --output= Output an xunit XML file to the file " + "specified in .\n" + " --enable-mixed-units Enable the per-test output to contain " + "mixed units (s/ms/us/ns).\n" + " --random-order[=] Randomize the order that the tests are " + "ran in. If the optional argument is not provided, then a " + "random starting seed is used.\n"); + goto cleanup; + } else if (0 == + UTEST_STRNCMP(argv[index], filter_str, strlen(filter_str))) { + /* user wants to filter what test cases run! */ + filter = argv[index] + strlen(filter_str); + } else if (0 == + UTEST_STRNCMP(argv[index], output_str, strlen(output_str))) { + utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+"); + } else if (0 == UTEST_STRNCMP(argv[index], list_str, strlen(list_str))) { + for (index = 0; index < utest_state.tests_length; index++) { + UTEST_PRINTF("%s\n", utest_state.tests[index].name); + } + /* when printing the test list, don't actually run the tests */ + return 0; + } else if (0 == UTEST_STRNCMP(argv[index], enable_mixed_units_str, + strlen(enable_mixed_units_str))) { + enable_mixed_units = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_with_seed_str, + strlen(random_order_with_seed_str))) { + seed = + UTEST_CAST(utest_uint32_t, + strtoul(argv[index] + strlen(random_order_with_seed_str), + UTEST_NULL, 10)); + random_order = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_str, + strlen(random_order_str))) { + const utest_int64_t ns = utest_ns(); + + // Some really poor pseudo-random using the current time. I do this + // because I really want to avoid using C's rand() because that'd mean our + // random would be affected by any srand() usage by the user (which I + // don't want). + seed = UTEST_CAST(utest_uint32_t, ns >> 32) * 31 + + UTEST_CAST(utest_uint32_t, ns & 0xffffffff); + random_order = 1; + } + } + + if (random_order) { + // Use Fisher-Yates with the Durstenfield's version to randomly re-order the + // tests. + for (index = utest_state.tests_length; index > 1; index--) { + // For the random order we'll use PCG. + const utest_uint32_t state = seed; + const utest_uint32_t word = + ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + const utest_uint32_t next = + ((word >> 22u) ^ word) % UTEST_CAST(utest_uint32_t, index); + + // Swap the randomly chosen element into the last location. + const struct utest_test_state_s copy = utest_state.tests[index - 1]; + utest_state.tests[index - 1] = utest_state.tests[next]; + utest_state.tests[next] = copy; + + // Move the seed onwards. + seed = seed * 747796405u + 2891336453u; + } + } + + for (index = 0; index < utest_state.tests_length; index++) { + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + ran_tests++; + } + + printf("%s[==========]%s Running %" UTEST_PRIu64 " test cases.\n", + colours[GREEN], colours[RESET], UTEST_CAST(utest_uint64_t, ran_tests)); + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + fprintf(utest_state.output, + "\n", + UTEST_CAST(utest_uint64_t, ran_tests)); + fprintf(utest_state.output, + "\n", + UTEST_CAST(utest_uint64_t, ran_tests)); + } + + for (index = 0; index < utest_state.tests_length; index++) { + int result = UTEST_TEST_PASSED; + utest_int64_t ns = 0; + + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + printf("%s[ RUN ]%s %s\n", colours[GREEN], colours[RESET], + utest_state.tests[index].name); + + if (utest_state.output) { + fprintf(utest_state.output, "", + utest_state.tests[index].name); + } + + ns = utest_ns(); + errno = 0; +#if defined(UTEST_HAS_EXCEPTIONS) + UTEST_SURPRESS_WARNING_BEGIN + try { + utest_state.tests[index].func(&result, utest_state.tests[index].index); + } catch (const std::exception &err) { + printf(" Exception : %s\n", err.what()); + result = UTEST_TEST_FAILURE; + } catch (...) { + printf(" Exception : Unknown\n"); + result = UTEST_TEST_FAILURE; + } + UTEST_SURPRESS_WARNING_END +#else + utest_state.tests[index].func(&result, utest_state.tests[index].index); +#endif + ns = utest_ns() - ns; + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + } + + // Record the failing test. + if (UTEST_TEST_FAILURE == result) { + const size_t failed_testcase_index = failed_testcases_length++; + failed_testcases = UTEST_PTR_CAST( + size_t *, utest_realloc(UTEST_PTR_CAST(void *, failed_testcases), + sizeof(size_t) * failed_testcases_length)); + if (UTEST_NULL != failed_testcases) { + failed_testcases[failed_testcase_index] = index; + } + failed++; + } else if (UTEST_TEST_SKIPPED == result) { + const size_t skipped_testcase_index = skipped_testcases_length++; + skipped_testcases = UTEST_PTR_CAST( + size_t *, utest_realloc(UTEST_PTR_CAST(void *, skipped_testcases), + sizeof(size_t) * skipped_testcases_length)); + if (UTEST_NULL != skipped_testcases) { + skipped_testcases[skipped_testcase_index] = index; + } + skipped++; + } + + { + const char *const units[] = {"ns", "us", "ms", "s", UTEST_NULL}; + unsigned int unit_index = 0; + utest_int64_t time = ns; + + if (enable_mixed_units) { + for (unit_index = 0; UTEST_NULL != units[unit_index]; unit_index++) { + if (10000 > time) { + break; + } + + time /= 1000; + } + } + + if (UTEST_TEST_FAILURE == result) { + printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[RED], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else if (UTEST_TEST_SKIPPED == result) { + printf("%s[ SKIPPED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[YELLOW], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else { + printf("%s[ OK ]%s %s (%" UTEST_PRId64 "%s)\n", colours[GREEN], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } + } + } + + printf("%s[==========]%s %" UTEST_PRIu64 " test cases ran.\n", colours[GREEN], + colours[RESET], ran_tests); + printf("%s[ PASSED ]%s %" UTEST_PRIu64 " tests.\n", colours[GREEN], + colours[RESET], ran_tests - failed - skipped); + + if (0 != skipped) { + printf("%s[ SKIPPED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[YELLOW], colours[RESET], skipped); + for (index = 0; index < skipped_testcases_length; index++) { + printf("%s[ SKIPPED ]%s %s\n", colours[YELLOW], colours[RESET], + utest_state.tests[skipped_testcases[index]].name); + } + } + + if (0 != failed) { + printf("%s[ FAILED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[RED], colours[RESET], failed); + for (index = 0; index < failed_testcases_length; index++) { + printf("%s[ FAILED ]%s %s\n", colours[RED], colours[RESET], + utest_state.tests[failed_testcases[index]].name); + } + } + + if (utest_state.output) { + fprintf(utest_state.output, "\n\n"); + } + +cleanup: + for (index = 0; index < utest_state.tests_length; index++) { + free(UTEST_PTR_CAST(void *, utest_state.tests[index].name)); + } + + free(UTEST_PTR_CAST(void *, skipped_testcases)); + free(UTEST_PTR_CAST(void *, failed_testcases)); + free(UTEST_PTR_CAST(void *, utest_state.tests)); + + if (utest_state.output) { + fclose(utest_state.output); + } + + return UTEST_CAST(int, failed); +} + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic pop +#endif +#endif + +/* + we need, in exactly one source file, define the global struct that will hold + the data we need to run utest. This macro allows the user to declare the + data without having to use the UTEST_MAIN macro, thus allowing them to write + their own main() function. +*/ +#define UTEST_STATE() struct utest_state_s utest_state = {0, 0, 0} + +/* + define a main() function to call into utest.h and start executing tests! A + user can optionally not use this macro, and instead define their own main() + function and manually call utest_main. The user must, in exactly one source + file, use the UTEST_STATE macro to declare a global struct variable that + utest requires. +*/ +#define UTEST_MAIN() \ + UTEST_STATE(); \ + int main(int argc, const char *const argv[]) { \ + return utest_main(argc, argv); \ + } + +#endif /* SHEREDOM_UTEST_H_INCLUDED */ diff --git a/include/utf8/test/utfmain.exe b/include/utf8/test/utfmain.exe new file mode 100644 index 0000000..ba54a22 Binary files /dev/null and b/include/utf8/test/utfmain.exe differ diff --git a/include/utf8/utf8.h b/include/utf8/utf8.h new file mode 100644 index 0000000..c83f812 --- /dev/null +++ b/include/utf8/utf8.h @@ -0,0 +1,1685 @@ +/* The latest version of this library is available on GitHub; + * https://github.com/sheredom/utf8.h */ + +/* This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to */ + +#ifndef SHEREDOM_UTF8_H_INCLUDED +#define SHEREDOM_UTF8_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: bytes padding added after construct */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +typedef __int32 utf8_int32_t; +#else +#include +typedef int32_t utf8_int32_t; +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#define utf8_nonnull +#define utf8_pure +#define utf8_restrict __restrict +#define utf8_weak __inline + +#ifdef __cplusplus +#define utf8_null NULL +#else +#define utf8_null 0 +#endif + +#if (defined(__cplusplus) && __cplusplus >= 201402L) +#define utf8_constexpr14 constexpr +#define utf8_constexpr14_impl constexpr +#else +/* constexpr and weak are incompatible. so only enable one of them */ +#define utf8_constexpr14 utf8_weak +#define utf8_constexpr14_impl +#endif + +#if defined(__cplusplus) && __cplusplus >= 202002L +using utf8_int8_t = char8_t; /* Introduced in C++20 */ +#else +typedef char utf8_int8_t; +#endif + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8casecmp(const utf8_int8_t *src1, const utf8_int8_t *src2); + +/* Append the utf8 string src onto the utf8 string dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Find the first match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8chr(const utf8_int8_t *src, utf8_int32_t chr); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. */ +utf8_constexpr14 utf8_nonnull utf8_pure int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +/* Copy the utf8 string src onto the memory allocated in dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints not from the utf8 string reject. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8cspn(const utf8_int8_t *src, const utf8_int8_t *reject); + +/* Duplicate the utf8 string src by getting its size, malloc'ing a new buffer + * copying over the data, and returning that. Or 0 if malloc failed. */ +utf8_weak utf8_int8_t *utf8dup(const utf8_int8_t *src); + +/* Number of utf8 codepoints in the utf8 string str, + * excluding the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8len(const utf8_int8_t *str); + +/* Similar to utf8len, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8nlen(const utf8_int8_t *str, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. Checking at most n bytes of each utf8 + * string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncasecmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Append the utf8 string src onto the utf8 string dst, + * writing at most n+1 bytes. Can produce an invalid utf8 + * string if n falls partway through a utf8 codepoint. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. Checking at most n + * bytes of each utf8 string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Copy the utf8 string src onto the memory allocated in dst. + * Copies at most n bytes. If n falls partway through a utf8 + * codepoint, or if dst doesn't have enough room for a null + * terminator, the final string will be cut short to preserve + * utf8 validity. */ + +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise */ +utf8_weak utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n); + +/* Locates the first occurrence in the utf8 string str of any byte in the + * utf8 string accept, or 0 if no match was found. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8pbrk(const utf8_int8_t *str, const utf8_int8_t *accept); + +/* Find the last match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8rchr(const utf8_int8_t *src, int chr); + +/* Number of bytes in the utf8 string str, + * including the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8size(const utf8_int8_t *str); + +/* Similar to utf8size, except that the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8size_lazy(const utf8_int8_t *str); + +/* Similar to utf8size, except that only at most n bytes of src are looked and + * the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8nsize_lazy(const utf8_int8_t *str, size_t n); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints from the utf8 string accept. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8spn(const utf8_int8_t *src, const utf8_int8_t *accept); + +/* The position of the utf8 string needle in the utf8 string haystack. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8str(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* The position of the utf8 string needle in the utf8 string haystack, case + * insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8casestr(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* Return 0 on success, or the position of the invalid + * utf8 codepoint on failure. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8valid(const utf8_int8_t *str); + +/* Similar to utf8valid, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8nvalid(const utf8_int8_t *str, size_t n); + +/* Given a null-terminated string, makes the string valid by replacing invalid + * codepoints with a 1-byte replacement. Returns 0 on success. */ +utf8_nonnull utf8_weak int utf8makevalid(utf8_int8_t *str, + const utf8_int32_t replacement); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the next utf8 codepoint after the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Calculates the size of the next utf8 codepoint in str. */ +utf8_constexpr14 utf8_nonnull size_t +utf8codepointcalcsize(const utf8_int8_t *str); + +/* Returns the size of the given codepoint in bytes. */ +utf8_constexpr14 size_t utf8codepointsize(utf8_int32_t chr); + +/* Write a codepoint to the given string, and return the address to the next + * place after the written codepoint. Pass how many bytes left in the buffer to + * n. If there is not enough space for the codepoint, this function returns + * null. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n); + +/* Returns 1 if the given character is lowercase, or 0 if it is not. */ +utf8_constexpr14 int utf8islower(utf8_int32_t chr); + +/* Returns 1 if the given character is uppercase, or 0 if it is not. */ +utf8_constexpr14 int utf8isupper(utf8_int32_t chr); + +/* Transform the given string into all lowercase codepoints. */ +utf8_nonnull utf8_weak void utf8lwr(utf8_int8_t *utf8_restrict str); + +/* Transform the given string into all uppercase codepoints. */ +utf8_nonnull utf8_weak void utf8upr(utf8_int8_t *utf8_restrict str); + +/* Make a codepoint lower case if possible. */ +utf8_constexpr14 utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); + +/* Make a codepoint upper case if possible. */ +utf8_constexpr14 utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the previous utf8 codepoint before the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Duplicate the utf8 string src by getting its size, calling alloc_func_ptr to + * copy over data to a new buffer, and returning that. Or 0 if alloc_func_ptr + * returned null. */ +utf8_weak utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise. */ +utf8_weak utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +#undef utf8_weak +#undef utf8_pure +#undef utf8_nonnull + +utf8_constexpr14_impl int utf8casecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + for (;;) { + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + + /* lower the srcs if required */ + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + /* lower the srcs if required */ + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } +} + +utf8_int8_t *utf8cat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl utf8_int8_t *utf8chr(const utf8_int8_t *src, + utf8_int32_t chr) { + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've made c into a 2 utf8 codepoint string, one for the chr we are + * seeking, another for the null terminating byte. Now use utf8str to + * search */ + return utf8str(src, c); +} + +utf8_constexpr14_impl int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + while (('\0' != *src1) || ('\0' != *src2)) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_constexpr14_impl int utf8coll(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +utf8_int8_t *utf8cpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* append null terminating byte */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl size_t utf8cspn(const utf8_int8_t *src, + const utf8_int8_t *reject) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *r = reject; + size_t offset = 0; + + while ('\0' != *r) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *r)) && (0 < offset)) { + return chars; + } else { + if (*r == src[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + r++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + r++; + } while (0x80 == (0xc0 & *r)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *r, so didn't get a chance to test it */ + if (0 < offset) { + return chars; + } + + /* the current utf8 codepoint in src did not match reject, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + src++; + } while ((0x80 == (0xc0 & *src))); + chars++; + } + + return chars; +} + +utf8_int8_t *utf8dup(const utf8_int8_t *src) { + return utf8dup_ex(src, utf8_null, utf8_null); +} + +utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *n = utf8_null; + + /* figure out how many bytes (including the terminator) we need to copy first + */ + size_t bytes = utf8size(src); + + if (alloc_func_ptr) { + n = alloc_func_ptr(user_data, bytes); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + n = (utf8_int8_t *)malloc(bytes); +#else + return utf8_null; +#endif + } + + if (utf8_null == n) { + /* out of memory so we bail */ + return utf8_null; + } else { + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes]) { + n[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + n[bytes] = '\0'; + return n; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8fry(const utf8_int8_t *str); + +utf8_constexpr14_impl size_t utf8len(const utf8_int8_t *str) { + return utf8nlen(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nlen(const utf8_int8_t *str, size_t n) { + const utf8_int8_t *t = str; + size_t length = 0; + + while ((size_t)(str - t) < n && '\0' != *str) { + if (0xf0 == (0xf8 & *str)) { + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else { /* if (0x00 == (0x80 & *s)) { */ + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } + + /* no matter the bytes we marched s forward by, it was + * only 1 utf8 codepoint */ + length++; + } + + if ((size_t)(str - t) > n) { + length--; + } + return length; +} + +utf8_constexpr14_impl int utf8ncasecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + do { + const utf8_int8_t *const s1 = src1; + const utf8_int8_t *const s2 = src2; + + /* first check that we have enough bytes left in n to contain an entire + * codepoint */ + if (0 == n) { + return 0; + } + + if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) { + const utf8_int32_t c1 = (0xe0 & *s1); + const utf8_int32_t c2 = (0xe0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) { + const utf8_int32_t c1 = (0xf0 & *s1); + const utf8_int32_t c2 = (0xf0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) { + const utf8_int32_t c1 = (0xf8 & *s1); + const utf8_int32_t c2 = (0xf8 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + n -= utf8codepointsize(src1_orig_cp); + + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } while (0 < n); + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte + * stopping if we run out of space */ + while (('\0' != *src) && (0 != n--)) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl int utf8ncmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + while ((0 != n--) && (('\0' != *src1) || ('\0' != *src2))) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + size_t index = 0, check_index = 0; + + if (n == 0) { + return dst; + } + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + for (index = 0; index < n; index++) { + d[index] = src[index]; + if ('\0' == src[index]) { + break; + } + } + + for (check_index = index - 1; + check_index > 0 && 0x80 == (0xc0 & d[check_index]); check_index--) { + /* just moving the index */ + } + + if (check_index < index && + ((index - check_index) < utf8codepointcalcsize(&d[check_index]) || + (index - check_index) == n)) { + index = check_index; + } + + /* append null terminating byte */ + for (; index < n; index++) { + d[index] = 0; + } + + return dst; +} + +utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n) { + return utf8ndup_ex(src, n, utf8_null, utf8_null); +} + +utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *c = utf8_null; + size_t bytes = 0; + + /* Find the end of the string or stop when n is reached */ + while ('\0' != src[bytes] && bytes < n) { + bytes++; + } + + /* In case bytes is actually less than n, we need to set it + * to be used later in the copy byte by byte. */ + n = bytes; + + if (alloc_func_ptr) { + c = alloc_func_ptr(user_data, bytes + 1); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + c = (utf8_int8_t *)malloc(bytes + 1); +#else + c = utf8_null; +#endif + } + + if (utf8_null == c) { + /* out of memory so we bail */ + return utf8_null; + } + + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes] && bytes < n) { + c[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + c[bytes] = '\0'; + return c; +} + +utf8_constexpr14_impl utf8_int8_t *utf8rchr(const utf8_int8_t *src, int chr) { + + utf8_int8_t *match = utf8_null; + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((int)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((int)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((int)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've created a 2 utf8 codepoint string in c that is + * the utf8 character asked for by chr, and a null + * terminating byte */ + + while ('\0' != *src) { + size_t offset = 0; + + while ((src[offset] == c[offset]) && ('\0' != src[offset])) { + offset++; + } + + if ('\0' == c[offset]) { + /* we found a matching utf8 code point */ + match = (utf8_int8_t *)src; + src += offset; + + if ('\0' == *src) { + break; + } + } else { + src += offset; + + /* need to march s along to next utf8 codepoint start + * (the next byte that doesn't match 0b10xxxxxx) */ + if ('\0' != *src) { + do { + src++; + } while (0x80 == (0xc0 & *src)); + } + } + } + + /* return the last match we found (or 0 if no match was found) */ + return match; +} + +utf8_constexpr14_impl utf8_int8_t *utf8pbrk(const utf8_int8_t *str, + const utf8_int8_t *accept) { + while ('\0' != *str) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *a is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + return (utf8_int8_t *)str; + } else { + if (*a == str[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + a++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* we found a match on the last utf8 codepoint */ + if (0 < offset) { + return (utf8_int8_t *)str; + } + + /* the current utf8 codepoint in src did not match accept, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + str++; + } while ((0x80 == (0xc0 & *str))); + } + + return utf8_null; +} + +utf8_constexpr14_impl size_t utf8size(const utf8_int8_t *str) { + return utf8size_lazy(str) + 1; +} + +utf8_constexpr14_impl size_t utf8size_lazy(const utf8_int8_t *str) { + return utf8nsize_lazy(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nsize_lazy(const utf8_int8_t *str, size_t n) { + size_t size = 0; + while (size < n && '\0' != str[size]) { + size++; + } + return size; +} + +utf8_constexpr14_impl size_t utf8spn(const utf8_int8_t *src, + const utf8_int8_t *accept) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + /* found a match, so increment the number of utf8 codepoints + * that have matched and stop checking whether any other utf8 + * codepoints in a match */ + chars++; + src += offset; + offset = 0; + break; + } else { + if (*a == src[offset]) { + offset++; + a++; + } else { + /* a could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning, */ + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *a, so didn't get a chance to test it */ + if (0 < offset) { + chars++; + src += offset; + continue; + } + + /* if a got to its terminating null byte, then we didn't find a match. + * Return the current number of matched utf8 codepoints */ + if ('\0' == *a) { + return chars; + } + } + + return chars; +} + +utf8_constexpr14_impl utf8_int8_t *utf8str(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + utf8_int32_t throwaway_codepoint = 0; + + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + while ('\0' != *haystack) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + + while (*haystack == *n && (*haystack != '\0' && *n != '\0')) { + n++; + haystack++; + } + + if ('\0' == *n) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } else { + /* h could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning + * starting from the current character */ + haystack = utf8codepoint(maybeMatch, &throwaway_codepoint); + } + } + + /* no match */ + return utf8_null; +} + +utf8_constexpr14_impl utf8_int8_t *utf8casestr(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + for (;;) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + utf8_int32_t h_cp = 0, n_cp = 0; + + /* Get the next code point and track it */ + const utf8_int8_t *nextH = haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + + while ((0 != h_cp) && (0 != n_cp)) { + h_cp = utf8lwrcodepoint(h_cp); + n_cp = utf8lwrcodepoint(n_cp); + + /* if we find a mismatch, bail out! */ + if (h_cp != n_cp) { + break; + } + + haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + } + + if (0 == n_cp) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } + + if (0 == h_cp) { + /* no match */ + return utf8_null; + } + + /* Roll back to the next code point in the haystack to test */ + haystack = nextH; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8valid(const utf8_int8_t *str) { + return utf8nvalid(str, SIZE_MAX); +} + +utf8_constexpr14_impl utf8_int8_t *utf8nvalid(const utf8_int8_t *str, + size_t n) { + const utf8_int8_t *t = str; + size_t consumed = 0; + + while ((void)(consumed = (size_t)(str - t)), consumed < n && '\0' != *str) { + const size_t remaining = n - consumed; + + if (0xf0 == (0xf8 & *str)) { + /* ensure that there's 4 bytes or more remaining */ + if (remaining < 4) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2])) || + (0x80 != (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 4 bytes */ + if ((remaining != 4) && (0x80 == (0xc0 & str[4]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 4-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x07 & str[0])) && (0 == (0x30 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* ensure that there's 3 bytes or more remaining */ + if (remaining < 3) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 3 bytes */ + if ((remaining != 3) && (0x80 == (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 3-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x0f & str[0])) && (0 == (0x20 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* ensure that there's 2 bytes or more remaining */ + if (remaining < 2) { + return (utf8_int8_t *)str; + } + + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & str[1])) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 2 bytes */ + if ((remaining != 2) && (0x80 == (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 4 bits of this 2-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if (0 == (0x1e & str[0])) { + return (utf8_int8_t *)str; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else if (0x00 == (0x80 & *str)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } else { + /* we have an invalid 0b1xxxxxxx utf8 code point entry */ + return (utf8_int8_t *)str; + } + } + + return utf8_null; +} + +int utf8makevalid(utf8_int8_t *str, const utf8_int32_t replacement) { + utf8_int8_t *read = str; + utf8_int8_t *write = read; + const utf8_int8_t r = (utf8_int8_t)replacement; + utf8_int32_t codepoint = 0; + + if (replacement > 0x7f) { + return -1; + } + + while ('\0' != *read) { + if (0xf0 == (0xf8 & *read)) { + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2])) || + (0x80 != (0xc0 & read[3]))) { + *write++ = r; + read++; + continue; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 4); + } else if (0xe0 == (0xf0 & *read)) { + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2]))) { + *write++ = r; + read++; + continue; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 3); + } else if (0xc0 == (0xe0 & *read)) { + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & read[1])) { + *write++ = r; + read++; + continue; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 2); + } else if (0x00 == (0x80 & *read)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 1); + } else { + /* if we got here then we've got a dangling continuation (0b10xxxxxx) */ + *write++ = r; + read++; + continue; + } + } + + *write = '\0'; + + return 0; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | + ((0x3f & str[2]) << 6) | (0x3f & str[3]); + str += 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]); + str += 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]); + str += 2; + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = str[0]; + str += 1; + } + + return (utf8_int8_t *)str; +} + +utf8_constexpr14_impl size_t utf8codepointcalcsize(const utf8_int8_t *str) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + return 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + return 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + return 2; + } + + /* 1 byte utf8 codepoint otherwise */ + return 1; +} + +utf8_constexpr14_impl size_t utf8codepointsize(utf8_int32_t chr) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + return 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + return 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + return 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + return 4; + } +} + +utf8_int8_t *utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + if (n < 1) { + return utf8_null; + } + str[0] = (utf8_int8_t)chr; + str += 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + if (n < 2) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)((chr >> 6) & 0x1f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 3) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)((chr >> 12) & 0x0f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 4) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)((chr >> 18) & 0x07)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 4; + } + + return str; +} + +utf8_constexpr14_impl int utf8islower(utf8_int32_t chr) { + return chr != utf8uprcodepoint(chr); +} + +utf8_constexpr14_impl int utf8isupper(utf8_int32_t chr) { + return chr != utf8lwrcodepoint(chr); +} + +void utf8lwr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +void utf8upr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8uprcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +utf8_constexpr14_impl utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) { + if (((0x0041 <= cp) && (0x005a >= cp)) || + ((0x00c0 <= cp) && (0x00d6 >= cp)) || + ((0x00d8 <= cp) && (0x00de >= cp)) || + ((0x0391 <= cp) && (0x03a1 >= cp)) || + ((0x03a3 <= cp) && (0x03ab >= cp)) || + ((0x0410 <= cp) && (0x042f >= cp))) { + cp += 32; + } else if ((0x0400 <= cp) && (0x040f >= cp)) { + cp += 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp |= 0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp += 1; + cp &= ~0x1; + } else { + switch (cp) { + default: + break; + case 0x0178: + cp = 0x00ff; + break; + case 0x0243: + cp = 0x0180; + break; + case 0x018e: + cp = 0x01dd; + break; + case 0x023d: + cp = 0x019a; + break; + case 0x0220: + cp = 0x019e; + break; + case 0x01b7: + cp = 0x0292; + break; + case 0x01c4: + cp = 0x01c6; + break; + case 0x01c7: + cp = 0x01c9; + break; + case 0x01ca: + cp = 0x01cc; + break; + case 0x01f1: + cp = 0x01f3; + break; + case 0x01f7: + cp = 0x01bf; + break; + case 0x0187: + cp = 0x0188; + break; + case 0x018b: + cp = 0x018c; + break; + case 0x0191: + cp = 0x0192; + break; + case 0x0198: + cp = 0x0199; + break; + case 0x01a7: + cp = 0x01a8; + break; + case 0x01ac: + cp = 0x01ad; + break; + case 0x01b8: + cp = 0x01b9; + break; + case 0x01bc: + cp = 0x01bd; + break; + case 0x01f4: + cp = 0x01f5; + break; + case 0x023b: + cp = 0x023c; + break; + case 0x0241: + cp = 0x0242; + break; + case 0x03fd: + cp = 0x037b; + break; + case 0x03fe: + cp = 0x037c; + break; + case 0x03ff: + cp = 0x037d; + break; + case 0x037f: + cp = 0x03f3; + break; + case 0x0386: + cp = 0x03ac; + break; + case 0x0388: + cp = 0x03ad; + break; + case 0x0389: + cp = 0x03ae; + break; + case 0x038a: + cp = 0x03af; + break; + case 0x038c: + cp = 0x03cc; + break; + case 0x038e: + cp = 0x03cd; + break; + case 0x038f: + cp = 0x03ce; + break; + case 0x0370: + cp = 0x0371; + break; + case 0x0372: + cp = 0x0373; + break; + case 0x0376: + cp = 0x0377; + break; + case 0x03f4: + cp = 0x03b8; + break; + case 0x03cf: + cp = 0x03d7; + break; + case 0x03f9: + cp = 0x03f2; + break; + case 0x03f7: + cp = 0x03f8; + break; + case 0x03fa: + cp = 0x03fb; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { + if (((0x0061 <= cp) && (0x007a >= cp)) || + ((0x00e0 <= cp) && (0x00f6 >= cp)) || + ((0x00f8 <= cp) && (0x00fe >= cp)) || + ((0x03b1 <= cp) && (0x03c1 >= cp)) || + ((0x03c3 <= cp) && (0x03cb >= cp)) || + ((0x0430 <= cp) && (0x044f >= cp))) { + cp -= 32; + } else if ((0x0450 <= cp) && (0x045f >= cp)) { + cp -= 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp &= ~0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp -= 1; + cp |= 0x1; + } else { + switch (cp) { + default: + break; + case 0x00ff: + cp = 0x0178; + break; + case 0x0180: + cp = 0x0243; + break; + case 0x01dd: + cp = 0x018e; + break; + case 0x019a: + cp = 0x023d; + break; + case 0x019e: + cp = 0x0220; + break; + case 0x0292: + cp = 0x01b7; + break; + case 0x01c6: + cp = 0x01c4; + break; + case 0x01c9: + cp = 0x01c7; + break; + case 0x01cc: + cp = 0x01ca; + break; + case 0x01f3: + cp = 0x01f1; + break; + case 0x01bf: + cp = 0x01f7; + break; + case 0x0188: + cp = 0x0187; + break; + case 0x018c: + cp = 0x018b; + break; + case 0x0192: + cp = 0x0191; + break; + case 0x0199: + cp = 0x0198; + break; + case 0x01a8: + cp = 0x01a7; + break; + case 0x01ad: + cp = 0x01ac; + break; + case 0x01b9: + cp = 0x01b8; + break; + case 0x01bd: + cp = 0x01bc; + break; + case 0x01f5: + cp = 0x01f4; + break; + case 0x023c: + cp = 0x023b; + break; + case 0x0242: + cp = 0x0241; + break; + case 0x037b: + cp = 0x03fd; + break; + case 0x037c: + cp = 0x03fe; + break; + case 0x037d: + cp = 0x03ff; + break; + case 0x03f3: + cp = 0x037f; + break; + case 0x03ac: + cp = 0x0386; + break; + case 0x03ad: + cp = 0x0388; + break; + case 0x03ae: + cp = 0x0389; + break; + case 0x03af: + cp = 0x038a; + break; + case 0x03cc: + cp = 0x038c; + break; + case 0x03cd: + cp = 0x038e; + break; + case 0x03ce: + cp = 0x038f; + break; + case 0x0371: + cp = 0x0370; + break; + case 0x0373: + cp = 0x0372; + break; + case 0x0377: + cp = 0x0376; + break; + case 0x03d1: + cp = 0x0398; + break; + case 0x03d7: + cp = 0x03cf; + break; + case 0x03f2: + cp = 0x03f9; + break; + case 0x03f8: + cp = 0x03f7; + break; + case 0x03fb: + cp = 0x03fa; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + const utf8_int8_t *s = (const utf8_int8_t *)str; + + if (0xf0 == (0xf8 & s[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | + ((0x3f & s[2]) << 6) | (0x3f & s[3]); + } else if (0xe0 == (0xf0 & s[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); + } else if (0xc0 == (0xe0 & s[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = s[0]; + } + + do { + s--; + } while ((0 != (0x80 & s[0])) && (0x80 == (0xc0 & s[0]))); + + return (utf8_int8_t *)s; +} + +#undef utf8_restrict +#undef utf8_constexpr14 +#undef utf8_null + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif /* SHEREDOM_UTF8_H_INCLUDED */ diff --git a/include/wcwidth.h b/include/wcwidth.h new file mode 100644 index 0000000..3b20f9f --- /dev/null +++ b/include/wcwidth.h @@ -0,0 +1,330 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +/* + This library has been slightly modified by anic17 in order to change + the type wchar_t to utf_int32_t, equivalent to int32_t + + While wchar_t is 32 bits in Linux and can represent any Unicode character, + wchar_t is just 16 bits in Windows (defined as a short) thus it cannot + represent correctly any Unicode character as the max value possible is + U+10FFFF, way bigger than U+FFFF (the limit for short data type) + + The following two lines have been added and all the cases where wchar_t + was used was replaced to utf8_int32_t to match the code in utf8.h library + (https://github.com/sheredom/utf8.h) + + */ +#include +typedef int32_t utf8_int32_t; + +struct interval { + int first; + int last; +}; + + + + +/* auxiliary function for binary search in interval table */ +static int bisearch(utf8_int32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that utf8_int32_t characters are encoded + * in ISO 10646. + */ + + +int mk_wcwidth(utf8_int32_t ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +int mk_wcswidth(const utf8_int32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +int mk_wcwidth_cjk(utf8_int32_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +int mk_wcswidth_cjk(const utf8_int32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} diff --git a/resources/newtrodit.png b/resources/newtrodit.png new file mode 100644 index 0000000..134d219 Binary files /dev/null and b/resources/newtrodit.png differ diff --git a/resources/screenshot_main.png b/resources/screenshot_main.png new file mode 100644 index 0000000..8332675 Binary files /dev/null and b/resources/screenshot_main.png differ diff --git a/src/core.c_ b/src/core.c_ new file mode 100644 index 0000000..7a2c432 --- /dev/null +++ b/src/core.c_ @@ -0,0 +1,251 @@ +#include "core.h" + +size_t utf8len_n(const char *s) +{ + if (!s) + return 0; + return utf8len(s); +} + +size_t utf8nlen_n(const char *s, size_t n) +{ + if (!s) + return 0; + return utf8nlen(s, n); +} + +size_t utf8len_null(char *s, size_t max_bytes) // Get the number of UTF-8 characters in a non-null terminated string with a known byte length +{ + if (!s) + return 0; + char *ptr = s; + utf8_int32_t cp=0; + size_t ulen = 0; + while (*ptr != '\0' && ulen < max_bytes) + { + ptr = utf8codepoint(ptr, &cp); // Get a pointer to the next codepoint + ulen++; // Increase the Unicode character counter + } + return ulen; +} + +size_t strlen_n(const char *s) +{ + if (!s) + return 0; + return strlen(s); +} + +int vt_settings(bool enabled) +{ +#ifdef _WIN32 + DWORD lmode; // Process ANSI escape sequences + + if (!GetConsoleMode(hStdout, &lmode)) + return 0; + if (enabled) + lmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING & ~DISABLE_NEWLINE_AUTO_RETURN; + else + lmode &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + + return SetConsoleMode(hStdout, lmode); +#endif + return 0; +} + +/* Safe version of strcpy() and strncpy() */ +char *strncpy_n(char *dest, const char *src, size_t count) +{ + // Better version that str8ncpy() because it always null terminates strings + + if (count) + { + memset(dest, 0, count); + strncat(dest, src, count); + return dest; + } + return NULL; +} + +size_t strrpbrk(char *s, char *find) // Reverse strpbrk, just like strrchr but for multiple characters +{ + size_t findlen = utf8len_n(find); + size_t slen = utf8len_n(s); + for (size_t i = slen; i > 0; i--) + { + for (size_t j = 0; j < findlen; j++) + { + if (s[i - 1] == find[j]) + return i - 1; + } + } + return 0; +} + +void *realloc_n(void *old, size_t old_sz, size_t new_sz) +{ + void *new = malloc(new_sz); + if (!new) + return NULL; + memcpy(new, old, old_sz); + free(old); + return new; +} + +int valid_file_name(char *filename) +{ + return utf8pbrk(filename, "*?\"<>|\x1b") == NULL; +} + +char *remove_quotes(char *s) +{ + size_t len = strlen_n(s); + if (s[0] == '\"' && s[len - 1] == '\"') + { + memmove(s, s + 1, len - 2); + s[len] = '\0'; + } + return s; +} + +int trim_message(char *msg, size_t max_len) +{ + size_t ulen = utf8len(msg); + if (ulen > max_len) + { + msg[max_len] = '\0'; + return 1; + } + return 0; +} + +int set_status_msg(bool display_once, char *msg, ...) +{ + va_list args; + va_start(args, msg); + + ed.status_msg = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(utf8_int32_t)); + if (!ed.status_msg) + return 0; + + size_t copyamount = DEFAULT_ALLOC_SIZE * sizeof(utf8_int32_t); + vsnprintf(ed.status_msg, copyamount, msg, args); + trim_message(ed.status_msg, ed.xsize - (fullCursorInfoDisplay ? 45 : 20)); + + ed.dirty = true; + ed.displayStatusOnce = display_once; + return 1; +} + +int clear_status_msg() +{ + if (ed.status_msg != NULL) + free(ed.status_msg); + + ed.status_msg = NULL; + ed.dirty = true; + return 1; +} + +int last_token_position(char *s, const char *token) +{ + + int lastpos = -1; + if (!s || !token) + return lastpos; + + for (size_t i = 0; i < strlen_n(s); i++) + { + for (size_t j = 0; j < strlen_n(token); j++) + { + if (s[i] == token[j]) + lastpos = i; + } + } + return lastpos; +} + +char *last_token(char *tok, const char *char_token) +{ + if (!tok || !char_token) + return tok; + + int pos = last_token_position(tok, char_token); + return tok + pos + 1; +} + +int allocate_buffer(File **tstack) +{ + *tstack = calloc(1, sizeof(File)); + (*tstack)->file_flags = IS_UNTITLED; + + (*tstack)->filename = calloc(MAX_PATH * sizeof(utf8_int32_t) + 1, sizeof(char)); + memcpy((*tstack)->filename, default_filename, MAX_PATH); + + (*tstack)->fwrite_time = time(NULL); + (*tstack)->fread_time = time(NULL); + (*tstack)->language = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); + memcpy((*tstack)->language, default_language, utf8len_n(default_language)); + (*tstack)->linenumber_wide = 3; + (*tstack)->linenumber_padding = 1; + (*tstack)->linecount = 0; + (*tstack)->xpos = 0; + (*tstack)->ypos = 1; + (*tstack)->size = 0; + (*tstack)->encoding = ENCODING_UTF8; + (*tstack)->encoding_bom_len = 0; + (*tstack)->post_load_rendering = true; + + (*tstack)->line = calloc(DEFAULT_ALLOC_LINES, sizeof(Line *)); + + for (size_t i = 0; i <= DEFAULT_ALLOC_LINES; i++) + create_line(*tstack, i); + + (*tstack)->line[0]->str = " Illegal row number. Report this issue to the GitHub repository."; // If for some reason line 0 is accessed + + (*tstack)->newline = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); + memcpy((*tstack)->newline, default_newline, utf8len_n(default_newline)); + + return 1; +} + +int free_buffer(File **tstack) +{ + + for (size_t i = 1; i < (*tstack)->alloc_lines; i++) + { + free((*tstack)->line[i]->render); + free((*tstack)->line[i]->str); + free((*tstack)->line[i]); + } + + // free((*tstack)->line); + + free((*tstack)->newline); + free((*tstack)->filename); + free((*tstack)->language); + + /* free((*tstack)->compilerinfo.path); + free((*tstack)->compilerinfo.flags); + free((*tstack)->compilerinfo.output); + + free((*tstack)->syntaxinfo.syntax_lang); + free((*tstack)->syntaxinfo.syntax_file); + free((*tstack)->syntaxinfo.separators); + free((*tstack)->syntaxinfo.comments); + + for (int i = 0; i < (*tstack)->syntaxinfo.keyword_count; i++) + { + free((*tstack)->syntaxinfo.keywords[i]); + } + + for (int i = 0; i < (*tstack)->syntaxinfo.comment_count; i++) + { + free((*tstack)->syntaxinfo.comments[i]); + } + + free((*tstack)->syntaxinfo.keywords); + free((*tstack)->syntaxinfo.color); + free((*tstack)->syntaxinfo.comments); */ + return 1; +} diff --git a/src/core.h_ b/src/core.h_ new file mode 100644 index 0000000..f36860f --- /dev/null +++ b/src/core.h_ @@ -0,0 +1,169 @@ +#ifndef CORE_H +#define CORE_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/wcwidth.h" +#include "utf8/utf8.h" + +#ifdef _WIN32 +#include +#endif + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#include "dialog.h" +#include "globals.h" + +const int DEFAULT_ALLOC_SIZE = 512; +const size_t LINE_SIZE = 64; +const size_t DEFAULT_ALLOC_LINES = 10; + +const size_t LINE_Y_INCREASE = 5; + +#if !defined ENABLE_VIRTUAL_TERMINAL_PROCESSING || !defined DISABLE_NEWLINE_AUTO_RETURN +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define DISABLE_NEWLINE_AUTO_RETURN 0x0008 +#endif + +enum file_flags_modifier +{ + IS_MODIFIED = 1, + IS_SAVED = 2, + IS_UNTITLED = 4, + IS_READONLY = 8, +}; + + +enum unicode_encodings +{ + ENCODING_UTF8 = 1, + ENCODING_UTF16LE = 2, + ENCODING_UTF16BE = 3, + ENCODING_UTF8BOM = 4, + ENCODING_UTF32LE = 5, + ENCODING_UTF32BE = 6, +}; + + + +const char newtrodit_version[] = "1.0"; +const char newtrodit_date[] = "2024/09/05"; + +typedef struct Line +{ + char *str; // Buffer that contains the line + char *render; + + size_t bufx; // Allocated line size + size_t render_bufx; // Allocated render size + + size_t len; // Line Length + size_t rlen; // Rendered length + size_t rnlen; // Rendered length without padding + + size_t ulen; // UTF-8 length +} Line; + +typedef struct Position +{ + size_t x; + size_t y; +} Position; + +typedef struct Select +{ + Position start; + Position end; + bool is_selected; +} Select; + +typedef struct File +{ + char *filename; + char *fullpath; + char *language; // Language (e.g: PowerShell, C, C++, etc.) + + // File information + size_t xpos, ypos; + size_t uxpos; // UTF-8 X position + size_t uwxpos; // UTF-8 width position + + Line **line; // Store each line file + size_t alloc_lines; + size_t linecount; + long long size; + + size_t linenumber_wide; + size_t linenumber_padding; + + char *newline; + unsigned int file_flags; + + unsigned int encoding; + size_t encoding_bom_len; + bool post_load_rendering; + + time_t fwrite_time; + time_t fread_time; + Select selection; + Position begin_display; + Position scroll_pos; +} File; // File information. This is used to store all the information about the file. + +typedef struct Editor +{ + size_t xsize, ysize; // Window size + int open_files; // Number of files open + int file_index; // Current file index + + char *log_file_name; + char *status_msg; + bool useLogFile; + bool lineNumbers; + bool dirty; + bool displayStatusOnce; + +} Editor; // Editor information + +typedef struct Color +{ + int r, g, b; + bool background; +} Color; + +Editor ed; +File **file; + +int end_editor(); +int init_editor(); +Line *create_line(File *tstack, size_t ypos); +int codepoint_width(const char *utf8_char, utf8_int32_t val); + +#ifdef _WIN32 +#include "win32/graphics_win32.h" +const char PATHDELIMS[] = "\\/"; // Set the path delimiters for Windows and Linux +#else +#include "linux/graphics_linux.h " +const char PATHDELIMS[] = "/"; +#endif + +#define _xpos file[ed.file_index]->xpos +#define _ypos file[ed.file_index]->ypos +#define _uxpos file[ed.file_index]->uxpos +#define _uwxpos file[ed.file_index]->uwxpos + + +#endif // CORE_H \ No newline at end of file diff --git a/src/dialog.c b/src/dialog.c new file mode 100644 index 0000000..67c69bb --- /dev/null +++ b/src/dialog.c @@ -0,0 +1,216 @@ +/* + * Newtrodit: A console text editor + * Copyright (C) 2021-2025 anic17 Software + * + * This file contains the SINGLE DEFINITIONS for all global dialog strings + * declared in dialog.h. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + */ + +#include "dialog.h" + +// --- Definitions (Storage Allocation) --- +// Note: No 'extern' keyword here, as this is where the memory is allocated. + +// Errors +char NEWTRODIT_ERROR_UNKNOWN[] = "Unknown error."; +char NEWTRODIT_ERROR_CLIPBOARD_COPY[] = "Cannot copy string to clipboard."; +char NEWTRODIT_ERROR_CLIPBOARD_PASTE[] = "Cannot paste string from clipboard."; + +char NEWTRODIT_ERROR_INVALID_XPOS[] = "Invalid X cursor position."; +char NEWTRODIT_ERROR_INVALID_YPOS[] = "Invalid row position."; +char NEWTRODIT_ERROR_MANUAL_INVALID_LINE[] = "Invalid line (maximum line number is %d)"; +char NEWTRODIT_ERROR_INVALID_POS_RESET[] = "Invalid position for cursor detected. Resetting coordinates."; +char NEWTRODIT_ERROR_WINDOW_TOO_SMALL[] = "Console window is too small. Please resize it."; +char NEWTRODIT_ERROR_MANUAL_TOO_BIG[] = "Manual file is too big."; +char NEWTRODIT_ERROR_MISSING_MANUAL[] = "Manual file is missing: "; +char NEWTRODIT_ERROR_INVALID_MANUAL[] = "Invalid manual file: "; +char NEWTRODIT_ERROR_LOADING_MANUAL[] = "Error loading manual file. This might be caused due to an outdated OS version "; + +char NEWTRODIT_ERROR_INVALID_INBOUND[] = "Invalid inbound key combination: %s"; +char NEWTRODIT_ERROR_RELOAD_SETTINGS[] = "Cannot reload settings. Settings file doesn't exist or it's corrupt."; +char NEWTRODIT_ERROR_OUT_OF_MEMORY[] = "Out of memory."; +char NEWTRODIT_ERROR_ALLOCATION_FAILED[] = "Memory allocation failed."; +char NEWTRODIT_ERROR_INVALID_SYNTAX[] = "Invalid syntax highlighting file: "; +char NEWTRODIT_ERROR_SYNTAX_RULES[] = "No syntax highlighting rules found."; +char NEWTRODIT_ERROR_FAILED_PROCESS_START[] = "Failed to start the process: "; +char NEWTRODIT_ERROR_FAILED_CLOSE_FILE[] = "Failed to close the file '%s'"; +char NEWTRODIT_ERROR_FAILED_CONSOLE_ATTRIB[] = "Failed to retrieve or set the console attributes."; + +char NEWTRODIT_ERROR_INVALID_MACRO[] = "Invalid macro: "; + +char NEWTRODIT_ERROR_INVALID_UNICODE_SEQUENCE[] = "Invalid Unicode byte sequence."; +char NEWTRODIT_ERROR_INVALID_UNICODE_POSITION[] = "Invalid Unicode string cursor position."; +char NEWTRODIT_ERROR_UNSUPPORTED_ENCODING[] = "Unsupported encoding found. Newtrodit only supports UTF-8 encoding."; + + +char NEWTRODIT_ERROR_TOO_MANY_FILES_OPEN[] = "Too many files open."; +char NEWTRODIT_ERROR_CANNOT_OPEN_DEVICE[] = "Cannot open device file: "; +char NEWTRODIT_ERROR_CONSOLE_HANDLE[] = "Cannot get console handle."; +char NEWTRODIT_ERROR_UNKNOWN_COMMAND[] = "Unknown command: '%s'"; +char NEWTRODIT_ERROR_INVALID_FILE_INDEX[] = "Invalid file index. Switching to the first file."; +char NEWTRODIT_ERROR_NEW_FILE[] = "Failed to open a new file."; +char NEWTRODIT_ERROR_REDIRECTED_TTY[] = "Newtrodit cannot be redirected to a file."; + + +// Internal errors +char NEWTRODIT_INTERNAL_EXPECTED_NULL[] = "Internal error: expected a non-null string"; + +// Crashes +char NEWTRODIT_CRASH_INVALID_SETTINGS[] = "Invalid settings file."; +char NEWTRODIT_CRASH_UNKNOWN_EXCEPTION[] = "Unknown exception."; + + +// Warnings +char NEWTRODIT_WARNING_SYNTAX_TOO_BIG[] = "Warning: Syntax highlighting file is too big. Using the first keywords."; +char NEWTRODIT_WARNING_READONLY_FILE[] = "Warning: File has read-only attribute."; + +// Command-line arguments +char NEWTRODIT_ERROR_MISSING_ARGUMENT[] = "Missing argument. See 'newtrodit --help'"; +char NEWTRODIT_ERROR_INVALID_COLOR[] = "Invalid color. Color range is between 0 and F."; + +// File IO error/info dialogs +char NEWTRODIT_FS_FILE_TOO_LARGE[] = "File too large: "; +char NEWTRODIT_FS_FILE_OPEN_ERR[] = "Failed to open the file: %s"; +char NEWTRODIT_FS_FILE_SAVE_ERR[] = "Failed to save the file: %s"; +char NEWTRODIT_FS_FILE_INVALID_NAME[] = "Invalid file name: "; +char NEWTRODIT_FS_FILE_NAME_TOO_LONG[] = "File name too long: "; +char NEWTRODIT_FS_FILE_NOT_FOUND[] = "File not found: "; +char NEWTRODIT_FS_DIR_NOT_FOUND[] = "Directory not found: %s"; + +char NEWTRODIT_FS_FILE_RENAME[] = "Cannot rename the file."; +char NEWTRODIT_FS_FILE_DELETE[] = "Failed to delete the file: "; +char NEWTRODIT_FS_SAME_FILE[] = "Cannot open the same file."; +char NEWTRODIT_FS_FOUND_FILES[] = "Showing %d of %d files matching the search pattern."; +char NEWTRODIT_FS_READONLY_SAVE[] = "Cannot write to a read-only file."; +char NEWTRODIT_FS_IS_A_DIRECTORY[] = "Is a directory: "; +char NEWTRODIT_FS_NOT_A_DIRECTORY[] = "Not a directory: %s"; +char NEWTRODIT_FS_NO_FILES_FOUND[] = "No files found matching the specified criteria."; + +char NEWTRODIT_FS_DISK_FULL[] = "Disk is full."; +char NEWTRODIT_FS_ACCESS_DENIED[] = "Access is denied: "; + +// Prompts +char NEWTRODIT_PROMPT_FIND_STRING[] = "String to find: "; +char NEWTRODIT_PROMPT_FIND_STRING_INSENSITIVE[] = "String to find (case insensitive): "; +char NEWTRODIT_PROMPT_REPLACE_STRING[] = "String to replace: "; +char NEWTRODIT_PROMPT_FOPEN[] = "File to open: "; +char NEWTRODIT_PROMPT_ALREADY_OPEN_TAB[] = "'%s' is already open in another tab. Do you want to switch to it? (y/n)"; + +char NEWTRODIT_PROMPT_GOTO_LINE[] = "Line number: "; +char NEWTRODIT_PROMPT_GOTO_COLUMN[] = "Column number: "; +char NEWTRODIT_PROMPT_RENAME_FILE[] = "New file name: "; +char NEWTRODIT_PROMPT_LOCATE_FILE[] = "File to locate: "; + +char NEWTRODIT_PROMPT_OVERWRITE[] = "File already exists. Overwrite? (y/n)"; +char NEWTRODIT_PROMPT_QUIT[] = "Are you sure you want to quit Newtrodit? (y/n)"; +char NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE[] = "File has been modified. Save changes? (y/n)"; +char NEWTRODIT_PROMPT_NEW_FILE[] = "Are you sure you want to create a new file? (y/n)"; +char NEWTRODIT_PROMPT_CLOSE_FILE[] = "Are you sure you want to close the file? (y/n)"; +char NEWTRODIT_PROMPT_RELOAD_FILE[] = "Are you sure you want to reload the file? (y/n)"; +char NEWTRODIT_PROMPT_RELOAD_SETTINGS[] = "Are you sure you want to reload the settings? (y/n)"; +char NEWTRODIT_PROMPT_FILE_CREATING[] = " doesn't exist. Do you want to create it? (y/n)"; // Must be added using join() later +char NEWTRODIT_PROMPT_REOPEN_FILE[] = "Do you want to reopen the file? (y/n)"; +char NEWTRODIT_PROMPT_MODIFIED_FILE_DISK[] = "File has been modified on disk. Do you want to reload it? (y/n)"; +char NEWTRODIT_PROMPT_SAVE_FILE[] = "File to save: "; +char NEWTRODIT_PROMPT_SAVE_FILE_AS[] = "Save file as: "; +char NEWTRODIT_PROMPT_CREATE_MACRO[] = "Macro command line: "; +char NEWTRODIT_PROMPT_SYNTAX_FILE[] = "Syntax highligthing file: "; +char NEWTRODIT_PROMPT_COMMAND_PALETTE[] = "Command palette: "; + + +char NEWTRODIT_PROMPT_FIRST_FILE_COMPARE[] = "First file to compare: "; +char NEWTRODIT_PROMPT_SECOND_FILE_COMPARE[] = "Second file to compare: "; + +// Informational dialogs +char NEWTRODIT_NO_ERROR[] = "No error."; +char NEWTRODIT_FILE_SAVED[] = "File saved successfully."; +char NEWTRODIT_FILE_OPENED[] = "File opened successfully."; +char NEWTRODIT_FILE_RELOADED[] = "File reloaded successfully."; +char NEWTRODIT_NEW_FILE_CREATED[] = "New file created successfully."; +char NEWTRODIT_FILE_CLOSED[] = "File closed successfully."; +char NEWTRODIT_FILE_RENAMED[] = "File renamed successfully to: "; +char NEWTRODIT_FILE_WROTE_BUFFER[] = "Wrote the buffer content to: "; +char NEWTRODIT_SETTINGS_RELOADED[] = "Settings reloaded successfully."; +char NEWTRODIT_SYNTAX_HIGHLIGHTING_LOADED[] = "Syntax highlighting rules for %s language successfully loaded."; +char NEWTRODIT_SYNTAX_HIGHLIGHTING_FAILED[] = "Failed to load syntax highlighting rules."; +char NEWTRODIT_LOCATE_END_REACHED[] = "End of directory reached."; + + +char NEWTRODIT_FIND_STRING_NOT_FOUND[] = "String not found: "; +char NEWTRODIT_FIND_CASE_SENSITIVE[] = "Case sensitive search: "; +char NEWTRODIT_FIND_MATCH_WHOLE_WORD[] = "Match whole word: "; +char NEWTRODIT_FIND_NO_MORE_MATCHES[] = "No more matches."; + +char NEWTRODIT_DEV_TOOLS[] = "Developer tools: "; +char NEWTRODIT_TAB_CONVERSION[] = "Tab conversion: "; +char NEWTRODIT_NULL_CONVERSION[] = "Null character to spaces conversion: "; +char NEWTRODIT_LINE_COUNT[] = "Line count: "; +char NEWTRODIT_SYNTAX_HIGHLIGHTING[] = "Syntax highlighting: "; +char NEWTRODIT_MACRO_SET[] = "Macro set: "; +char NEWTRODIT_OLD_KEYBINDINGS[] = "Old keybindings: "; +char NEWTRODIT_AUTO_SYNTAX_LOAD[] = "Automatically load syntax highlighting rules for supported languages: "; +char NEWTRODIT_FILE_COMPARE_DIFF[] = "Difference found in byte number "; +char NEWTRODIT_FILE_COMPARE_NODIFF[] = "No difference found between files."; +char NEWTRODIT_LOADING_COMPARING_FILES[] = "Comparing files '%s' and '%s'. This may take a while."; +char NEWTRODIT_CURRENT_FILE_SWITCHED[] = "Editing now: "; +char NEWTRODIT_MOUSE[] = "Mouse: "; + +char NEWTRODIT_SHOWING_PREVIOUS_FILE[] = "Showing previous file"; +char NEWTRODIT_SHOWING_NEXT_FILE[] = "Showing next file"; +char NEWTRODIT_SWITCHING_FILE[] = "Switching to file"; +char NEWTRODIT_INFO_NO_FILES_TO_SWITCH[] = "No files to switch to."; +char NEWTRODIT_CLIPBOARD_COPIED[] = "Path copied to the system's clipboard (%s)"; + +// Other dialogs + +char NEWTRODIT_DIALOG_BOTTOM_HELP[] = "For help, press F1"; +char NEWTRODIT_DIALOG_MANUAL[] = "Ctrl-X Close help | Alt-F4 Quit Newtrodit"; +char NEWTRODIT_DIALOG_MANUAL_TITLE[] = " Newtrodit help"; +char NEWTRODIT_DIALOG_BOTTOM_LOCATE[] = " | Space Next page | Ctrl-B Go back | Ctrl-D Go to dir | Ctrl-X Quit"; +char NEWTRODIT_DIALOG_LOCATE_POS[] = "Showing %d-%d out of %d files (%llu total bytes)"; + + +char NEWTRODIT_DIALOG_ENABLED[] = "Enabled"; +char NEWTRODIT_DIALOG_DISABLED[] = "Disabled"; +char NEWTRODIT_FUNCTION_ABORTED[] = "Function aborted."; + +// Constants used internally + +char NEWTRODIT_MANUAL_MAGIC_NUMBER[] = "$NEWTRODIT_MANUAL"; + +char NEWTRODIT_SYNTAX_CAPITAL[] = "$CAPITAL"; +char NEWTRODIT_SYNTAX_CAPITAL_COLOR[] = "$CAPITAL_COLOR"; +char NEWTRODIT_SYNTAX_CAPITAL_MIN[] = "$CAPITAL_MIN"; +char NEWTRODIT_SYNTAX_COMMENT[] = "$COMMENT"; +char NEWTRODIT_SYNTAX_COMMENT_COLOR[] = "$COMMENT_COLOR"; +char NEWTRODIT_SYNTAX_DEFAULT_COLOR[] = "$DEFAULT_COLOR"; +char NEWTRODIT_SYNTAX_LANGUAGE[] = "$LANGUAGE"; +char NEWTRODIT_SYNTAX_MAGIC_NUMBER[] = "$NEWTRODIT_SYNTAX"; +char NEWTRODIT_SYNTAX_NUMBER_COLOR[] = "$NUMBER_COLOR"; +char NEWTRODIT_SYNTAX_QUOTE_COLOR[] = "$QUOTE_COLOR"; +char NEWTRODIT_SYNTAX_SEPARATORS[] = "$SEPARATORS"; +char NEWTRODIT_SYNTAX_SINGLE_QUOTES[] = "$SINGLE_QUOTES"; +char NEWTRODIT_SYNTAX_FINISH_QUOTES[] = "$FINISH_QUOTES"; + + +char NEWTRODIT_MACRO_CURRENT_FILE[] = "$FILE"; +char NEWTRODIT_MACRO_FULL_PATH[] = "$PATH"; +char NEWTRODIT_MACRO_CURRENT_DIR[] = "$DIR"; +char NEWTRODIT_MACRO_CURRENT_EXTENSION[] = "$EXT"; +char NEWTRODIT_MACRO_CURRENT_BASENAME[] = "$BASENAME"; +char NEWTRODIT_MACRO_CURRENT_DRIVE[] = "$DRIVE"; +char NEWTRODIT_SYNTAX_ENCLOSING[] = "$ENCLOSING"; \ No newline at end of file diff --git a/src/dialog.h b/src/dialog.h index 879fe9b..69cc9e2 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -1,208 +1,217 @@ -/* - Newtrodit: A console text editor - Copyright (C) 2021-2023 anic17 Software - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see - -*/ - -// Errors - -char NEWTRODIT_ERROR_UNKNOWN[] = "Unknown error."; -char NEWTRODIT_ERROR_CLIPBOARD_COPY[] = "Cannot copy string to clipboard."; -char NEWTRODIT_ERROR_CLIPBOARD_PASTE[] = "Cannot paste string from clipboard."; - -char NEWTRODIT_ERROR_INVALID_XPOS[] = "Invalid X position for cursor."; -char NEWTRODIT_ERROR_INVALID_YPOS[] = "Invalid row position."; -char NEWTRODIT_ERROR_MANUAL_INVALID_LINE[] = "Invalid line (maximum line number is %d)"; // I could use NEWTRODIT_ERROR_INVALID_YPOS but I prefer more specific errors (or messages) -char NEWTRODIT_ERROR_INVALID_POS_RESET[] = "Invalid position for cursor detected. Resetting coordinates."; -char NEWTRODIT_ERROR_WINDOW_TOO_SMALL[] = "Console window is too small. Please resize it."; -char NEWTRODIT_ERROR_MANUAL_TOO_BIG[] = "Manual file is too big."; -char NEWTRODIT_ERROR_MISSING_MANUAL[] = "Manual file is missing: "; -char NEWTRODIT_ERROR_INVALID_MANUAL[] = "Invalid manual file: "; -char NEWTRODIT_ERROR_LOADING_MANUAL[] = "Error loading manual file. This might be caused due to an outdated OS version "; - -char NEWTRODIT_ERROR_INVALID_INBOUND[] = "Invalid inbound key combination: %s"; -char NEWTRODIT_ERROR_RELOAD_SETTINGS[] = "Cannot reload settings. Settings file doesn't exist or it's corrupt."; -char NEWTRODIT_ERROR_OUT_OF_MEMORY[] = "Out of memory."; -char NEWTRODIT_ERROR_ALLOCATION_FAILED[] = "Memory allocation failed."; -char NEWTRODIT_ERROR_INVALID_SYNTAX[] = "Invalid syntax highlighting file: "; -char NEWTRODIT_ERROR_SYNTAX_RULES[] = "No syntax highlighting rules found."; -char NEWTRODIT_ERROR_FAILED_PROCESS_START[] = "Failed to start the process: "; -char NEWTRODIT_ERROR_FAILED_CLOSE_FILE[] = "Failed to close the file '%s'"; - -char NEWTRODIT_ERROR_INVALID_MACRO[] = "Invalid macro: "; -char NEWTRODIT_ERROR_INVALID_UNICODE_SEQUENCE[] = "Invalid Unicode byte sequence."; -char NEWTRODIT_ERROR_TOO_MANY_FILES_OPEN[] = "Too many files open."; -char NEWTRODIT_ERROR_CANNOT_OPEN_DEVICE[] = "Cannot open device file: "; -char NEWTRODIT_ERROR_CONSOLE_HANDLE[] = "Cannot get console handle."; -char NEWTRODIT_ERROR_UNKNOWN_COMMAND[] = "Unknown command: '%s'"; -char NEWTRODIT_LICENSE_INVALID_LICENSE[] = "Detected an invalid or outdated license file."; -char NEWTRODIT_LICENSE_MISSING_LICENSE[] = "License file is missing: "; -char NEWTRODIT_ERROR_INVALID_FILE_INDEX[] = "Invalid file index. Switching to the first file."; -char NEWTRODIT_ERROR_NEW_FILE[] = "Failed to open a new file."; -char NEWTRODIT_ERROR_REDIRECTED_TTY[] = "Newtrodit cannot be redirected to a file."; - - -// Internal errors -char NEWTRODIT_INTERNAL_EXPECTED_NULL[] = "Internal error: expected a non-null string"; - -// Crashes - -char NEWTRODIT_CRASH_INVALID_SETTINGS[] = "Invalid settings file."; -char NEWTRODIT_CRASH_UNKNOWN_EXCEPTION[] = "Unknown exception."; - - -// Warnings -char NEWTRODIT_WARNING_SYNTAX_TOO_BIG[] = "Warning: Syntax highlighting file is too big. Using the first keywords."; -char NEWTRODIT_WARNING_READONLY_FILE[] = "Warning: File has read-only attribute."; - -// Command-line arguments -char NEWTRODIT_ERROR_MISSING_ARGUMENT[] = "Missing argument. See 'newtrodit --help'"; -char NEWTRODIT_ERROR_INVALID_COLOR[] = "Invalid color. Color range is between 0 and F."; - -// File IO error/info dialogs -char NEWTRODIT_FS_FILE_TOO_LARGE[] = "File too large: "; -char NEWTRODIT_FS_FILE_OPEN_ERR[] = "Failed to open the file: %s"; -char NEWTRODIT_FS_FILE_SAVE_ERR[] = "Cannot save the file."; -char NEWTRODIT_FS_FILE_INVALID_NAME[] = "Invalid file name: "; -char NEWTRODIT_FS_FILE_NAME_TOO_LONG[] = "File name too long: "; -char NEWTRODIT_FS_FILE_NOT_FOUND[] = "File not found: "; -char NEWTRODIT_FS_DIR_NOT_FOUND[] = "Directory not found: %s"; - -char NEWTRODIT_FS_FILE_RENAME[] = "Cannot rename the file."; -char NEWTRODIT_FS_FILE_DELETE[] = "Failed to delete the file: "; -char NEWTRODIT_FS_SAME_FILE[] = "Cannot open the same file."; -char NEWTRODIT_FS_FOUND_FILES[] = "Showing %d of %d files matching the search pattern."; -char NEWTRODIT_FS_READONLY_SAVE[] = "Cannot write to a read-only file."; -char NEWTRODIT_FS_IS_A_DIRECTORY[] = "Is a directory: "; -char NEWTRODIT_FS_NOT_A_DIRECTORY[] = "Not a directory: %s"; -char NEWTRODIT_FS_NO_FILES_FOUND[] = "No files found matching the specified criteria."; - - - -char NEWTRODIT_FS_DISK_FULL[] = "Disk is full."; -char NEWTRODIT_FS_ACCESS_DENIED[] = "Access is denied: "; - -// Prompts - -char NEWTRODIT_PROMPT_FIND_STRING[] = "String to find: "; -char NEWTRODIT_PROMPT_FIND_STRING_INSENSITIVE[] = "String to find (case insensitive): "; -char NEWTRODIT_PROMPT_REPLACE_STRING[] = "String to replace: "; -char NEWTRODIT_PROMPT_FOPEN[] = "File to open: "; -char NEWTRODIT_PROMPT_ALREADY_OPEN_TAB[] = "'%s' is already open in another tab. Do you want to switch to it? (y/n)"; - -char NEWTRODIT_PROMPT_GOTO_LINE[] = "Line number: "; -char NEWTRODIT_PROMPT_GOTO_COLUMN[] = "Column number: "; -char NEWTRODIT_PROMPT_RENAME_FILE[] = "New file name: "; -char NEWTRODIT_PROMPT_LOCATE_FILE[] = "File to locate: "; - -char NEWTRODIT_PROMPT_OVERWRITE[] = "File already exists. Overwrite? (y/n)"; -char NEWTRODIT_PROMPT_QUIT[] = "Are you sure you want to quit Newtrodit? (y/n)"; -char NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE[] = "File has been modified. Save changes? (y/n)"; -char NEWTRODIT_PROMPT_NEW_FILE[] = "Are you sure you want to create a new file? (y/n)"; -char NEWTRODIT_PROMPT_CLOSE_FILE[] = "Are you sure you want to close the file? (y/n)"; -char NEWTRODIT_PROMPT_RELOAD_FILE[] = "Are you sure you want to reload the file? (y/n)"; -char NEWTRODIT_PROMPT_RELOAD_SETTINGS[] = "Are you sure you want to reload the settings? (y/n)"; -char NEWTRODIT_PROMPT_FILE_CREATING[] = " doesn't exist. Do you want to create it? (y/n)"; // Must be added using join() later -char NEWTRODIT_PROMPT_REOPEN_FILE[] = "Do you want to reopen the file? (y/n)"; -char NEWTRODIT_PROMPT_MODIFIED_FILE_DISK[] = "File has been modified on disk. Do you want to reload it? (y/n)"; -char NEWTRODIT_PROMPT_SAVE_FILE[] = "File to save: "; -char NEWTRODIT_PROMPT_SAVE_FILE_AS[] = "Save file as: "; -char NEWTRODIT_PROMPT_CREATE_MACRO[] = "Macro command line: "; -char NEWTRODIT_PROMPT_SYNTAX_FILE[] = "Syntax highligthing file: "; -char NEWTRODIT_PROMPT_COMMAND_PALETTE[] = "Command palette: "; - - -char NEWTRODIT_PROMPT_FIRST_FILE_COMPARE[] = "First file to compare: "; -char NEWTRODIT_PROMPT_SECOND_FILE_COMPARE[] = "Second file to compare: "; - -// Informational dialogs -char NEWTRODIT_NO_ERROR[] = "No error."; -char NEWTRODIT_FILE_SAVED[] = "File saved successfully."; -char NEWTRODIT_FILE_RELOADED[] = "File reloaded successfully."; -char NEWTRODIT_NEW_FILE_CREATED[] = "New file created successfully."; -char NEWTRODIT_FILE_CLOSED[] = "File closed successfully."; -char NEWTRODIT_FILE_RENAMED[] = "File renamed successfully to: "; -char NEWTRODIT_FILE_WROTE_BUFFER[] = "Wrote the buffer content to: "; -char NEWTRODIT_SETTINGS_RELOADED[] = "Settings reloaded successfully."; -char NEWTRODIT_SYNTAX_HIGHLIGHTING_LOADED[] = "Syntax highlighting rules for %s language successfully loaded."; -char NEWTRODIT_SYNTAX_HIGHLIGHTING_FAILED[] = "Failed to load syntax highlighting rules."; -char NEWTRODIT_LOCATE_END_REACHED[] = "End of directory reached."; - - -char NEWTRODIT_FIND_STRING_NOT_FOUND[] = "String not found: "; -char NEWTRODIT_FIND_CASE_SENSITIVE[] = "Case sensitive search: "; -char NEWTRODIT_FIND_MATCH_WHOLE_WORD[] = "Match whole word: "; -char NEWTRODIT_FIND_NO_MORE_MATCHES[] = "No more matches."; - -char NEWTRODIT_DEV_TOOLS[] = "Developer tools: "; -char NEWTRODIT_TAB_CONVERSION[] = "Tab conversion: "; -char NEWTRODIT_NULL_CONVERSION[] = "Null character to spaces conversion: "; -char NEWTRODIT_LINE_COUNT[] = "Line count: "; -char NEWTRODIT_SYNTAX_HIGHLIGHTING[] = "Syntax highlighting: "; -char NEWTRODIT_MACRO_SET[] = "Macro set: "; -char NEWTRODIT_OLD_KEYBINDINGS[] = "Old keybindings: "; -char NEWTRODIT_AUTO_SYNTAX_LOAD[] = "Automatically load syntax highlighting rules for supported languages: "; -char NEWTRODIT_FILE_COMPARE_DIFF[] = "Difference found in byte number "; -char NEWTRODIT_FILE_COMPARE_NODIFF[] = "No difference found between files."; -char NEWTRODIT_LOADING_COMPARING_FILES[] = "Comparing files '%s' and '%s'. This may take a while."; -char NEWTRODIT_CURRENT_FILE_SWITCHED[] = "Editing now: "; -char NEWTRODIT_MOUSE[] = "Mouse: "; - -char NEWTRODIT_SHOWING_PREVIOUS_FILE[] = "Showing previous file"; -char NEWTRODIT_SHOWING_NEXT_FILE[] = "Showing next file"; -char NEWTRODIT_SWITCHING_FILE[] = "Switching to file"; -char NEWTRODIT_INFO_NO_FILES_TO_SWITCH[] = "No files to switch to."; -char NEWTRODIT_CLIPBOARD_COPIED[] = "Path copied to the system's clipboard (%s)"; - -// Other dialogs - -char NEWTRODIT_DIALOG_BOTTOM_HELP[] = "For help, press F1 | "; -char NEWTRODIT_DIALOG_MANUAL[] = "Ctrl-X Close help | Alt-F4 Quit Newtrodit"; -char NEWTRODIT_DIALOG_MANUAL_TITLE[] = " Newtrodit help"; -char NEWTRODIT_DIALOG_BOTTOM_LOCATE[] = " | Space Next page | Ctrl-B Go back | Ctrl-D Go to dir | Ctrl-X Quit"; -char NEWTRODIT_DIALOG_LOCATE_POS[] = "Showing %d-%d out of %d files (%llu total bytes)"; - - -char NEWTRODIT_DIALOG_ENABLED[] = "Enabled"; -char NEWTRODIT_DIALOG_DISABLED[] = "Disabled"; -char NEWTRODIT_FUNCTION_ABORTED[] = "Function aborted."; - -// Constants used internally - -char NEWTRODIT_MANUAL_MAGIC_NUMBER[] = "$NEWTRODIT_MANUAL"; - -char NEWTRODIT_SYNTAX_CAPITAL[] = "$CAPITAL"; -char NEWTRODIT_SYNTAX_CAPITAL_COLOR[] = "$CAPITAL_COLOR"; -char NEWTRODIT_SYNTAX_CAPITAL_MIN[] = "$CAPITAL_MIN"; -char NEWTRODIT_SYNTAX_COMMENT[] = "$COMMENT"; -char NEWTRODIT_SYNTAX_COMMENT_COLOR[] = "$COMMENT_COLOR"; -char NEWTRODIT_SYNTAX_DEFAULT_COLOR[] = "$DEFAULT_COLOR"; -char NEWTRODIT_SYNTAX_LANGUAGE[] = "$LANGUAGE"; -char NEWTRODIT_SYNTAX_MAGIC_NUMBER[] = "$NEWTRODIT_SYNTAX"; -char NEWTRODIT_SYNTAX_NUMBER_COLOR[] = "$NUMBER_COLOR"; -char NEWTRODIT_SYNTAX_QUOTE_COLOR[] = "$QUOTE_COLOR"; -char NEWTRODIT_SYNTAX_SEPARATORS[] = "$SEPARATORS"; -char NEWTRODIT_SYNTAX_SINGLE_QUOTES[] = "$SINGLE_QUOTES"; -char NEWTRODIT_SYNTAX_FINISH_QUOTES[] = "$FINISH_QUOTES"; - - -char NEWTRODIT_MACRO_CURRENT_FILE[] = "$FILE"; -char NEWTRODIT_MACRO_FULL_PATH[] = "$PATH"; -char NEWTRODIT_MACRO_CURRENT_DIR[] = "$DIR"; -char NEWTRODIT_MACRO_CURRENT_EXTENSION[] = "$EXT"; -char NEWTRODIT_MACRO_CURRENT_BASENAME[] = "$BASENAME"; -char NEWTRODIT_MACRO_CURRENT_DRIVE[] = "$DRIVE"; -char NEWTRODIT_SYNTAX_ENCLOSING[] = "$ENCLOSING"; +/* + * Newtrodit: A console text editor + * Copyright (C) 2021-2025 anic17 Software + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + */ + +#ifndef DIALOG_H +#define DIALOG_H + +// --- IMPORTANT: All strings are now DECLARED using 'extern' --- +// This tells the compiler that the string exists, but the storage +// is allocated in dialog.c (or another single .c file). + +// Errors +extern char NEWTRODIT_ERROR_UNKNOWN[]; +extern char NEWTRODIT_ERROR_CLIPBOARD_COPY[]; +extern char NEWTRODIT_ERROR_CLIPBOARD_PASTE[]; + +extern char NEWTRODIT_ERROR_INVALID_XPOS[]; +extern char NEWTRODIT_ERROR_INVALID_YPOS[]; +extern char NEWTRODIT_ERROR_MANUAL_INVALID_LINE[]; +extern char NEWTRODIT_ERROR_INVALID_POS_RESET[]; +extern char NEWTRODIT_ERROR_WINDOW_TOO_SMALL[]; +extern char NEWTRODIT_ERROR_MANUAL_TOO_BIG[]; +extern char NEWTRODIT_ERROR_MISSING_MANUAL[]; +extern char NEWTRODIT_ERROR_INVALID_MANUAL[]; +extern char NEWTRODIT_ERROR_LOADING_MANUAL[]; + +extern char NEWTRODIT_ERROR_INVALID_INBOUND[]; +extern char NEWTRODIT_ERROR_RELOAD_SETTINGS[]; +extern char NEWTRODIT_ERROR_OUT_OF_MEMORY[]; +extern char NEWTRODIT_ERROR_ALLOCATION_FAILED[]; +extern char NEWTRODIT_ERROR_INVALID_SYNTAX[]; +extern char NEWTRODIT_ERROR_SYNTAX_RULES[]; +extern char NEWTRODIT_ERROR_FAILED_PROCESS_START[]; +extern char NEWTRODIT_ERROR_FAILED_CLOSE_FILE[]; +extern char NEWTRODIT_ERROR_FAILED_CONSOLE_ATTRIB[]; + +extern char NEWTRODIT_ERROR_INVALID_MACRO[]; + +extern char NEWTRODIT_ERROR_INVALID_UNICODE_SEQUENCE[]; +extern char NEWTRODIT_ERROR_INVALID_UNICODE_POSITION[]; +extern char NEWTRODIT_ERROR_UNSUPPORTED_ENCODING[]; + + +extern char NEWTRODIT_ERROR_TOO_MANY_FILES_OPEN[]; +extern char NEWTRODIT_ERROR_CANNOT_OPEN_DEVICE[]; +extern char NEWTRODIT_ERROR_CONSOLE_HANDLE[]; +extern char NEWTRODIT_ERROR_UNKNOWN_COMMAND[]; +extern char NEWTRODIT_ERROR_INVALID_FILE_INDEX[]; +extern char NEWTRODIT_ERROR_NEW_FILE[]; +extern char NEWTRODIT_ERROR_REDIRECTED_TTY[]; + + +// Internal errors +extern char NEWTRODIT_INTERNAL_EXPECTED_NULL[]; + +// Crashes +extern char NEWTRODIT_CRASH_INVALID_SETTINGS[]; +extern char NEWTRODIT_CRASH_UNKNOWN_EXCEPTION[]; + + +// Warnings +extern char NEWTRODIT_WARNING_SYNTAX_TOO_BIG[]; +extern char NEWTRODIT_WARNING_READONLY_FILE[]; + +// Command-line arguments +extern char NEWTRODIT_ERROR_MISSING_ARGUMENT[]; +extern char NEWTRODIT_ERROR_INVALID_COLOR[]; + +// File IO error/info dialogs +extern char NEWTRODIT_FS_FILE_TOO_LARGE[]; +extern char NEWTRODIT_FS_FILE_OPEN_ERR[]; +extern char NEWTRODIT_FS_FILE_SAVE_ERR[]; +extern char NEWTRODIT_FS_FILE_INVALID_NAME[]; +extern char NEWTRODIT_FS_FILE_NAME_TOO_LONG[]; +extern char NEWTRODIT_FS_FILE_NOT_FOUND[]; +extern char NEWTRODIT_FS_DIR_NOT_FOUND[]; + +extern char NEWTRODIT_FS_FILE_RENAME[]; +extern char NEWTRODIT_FS_FILE_DELETE[]; +extern char NEWTRODIT_FS_SAME_FILE[]; +extern char NEWTRODIT_FS_FOUND_FILES[]; +extern char NEWTRODIT_FS_READONLY_SAVE[]; +extern char NEWTRODIT_FS_IS_A_DIRECTORY[]; +extern char NEWTRODIT_FS_NOT_A_DIRECTORY[]; +extern char NEWTRODIT_FS_NO_FILES_FOUND[]; + +extern char NEWTRODIT_FS_DISK_FULL[]; +extern char NEWTRODIT_FS_ACCESS_DENIED[]; + +// Prompts +extern char NEWTRODIT_PROMPT_FIND_STRING[]; +extern char NEWTRODIT_PROMPT_FIND_STRING_INSENSITIVE[]; +extern char NEWTRODIT_PROMPT_REPLACE_STRING[]; +extern char NEWTRODIT_PROMPT_FOPEN[]; +extern char NEWTRODIT_PROMPT_ALREADY_OPEN_TAB[]; + +extern char NEWTRODIT_PROMPT_GOTO_LINE[]; +extern char NEWTRODIT_PROMPT_GOTO_COLUMN[]; +extern char NEWTRODIT_PROMPT_RENAME_FILE[]; +extern char NEWTRODIT_PROMPT_LOCATE_FILE[]; + +extern char NEWTRODIT_PROMPT_OVERWRITE[]; +extern char NEWTRODIT_PROMPT_QUIT[]; +extern char NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE[]; +extern char NEWTRODIT_PROMPT_NEW_FILE[]; +extern char NEWTRODIT_PROMPT_CLOSE_FILE[]; +extern char NEWTRODIT_PROMPT_RELOAD_FILE[]; +extern char NEWTRODIT_PROMPT_RELOAD_SETTINGS[]; +extern char NEWTRODIT_PROMPT_FILE_CREATING[]; +extern char NEWTRODIT_PROMPT_REOPEN_FILE[]; +extern char NEWTRODIT_PROMPT_MODIFIED_FILE_DISK[]; +extern char NEWTRODIT_PROMPT_SAVE_FILE[]; +extern char NEWTRODIT_PROMPT_SAVE_FILE_AS[]; +extern char NEWTRODIT_PROMPT_CREATE_MACRO[]; +extern char NEWTRODIT_PROMPT_SYNTAX_FILE[]; +extern char NEWTRODIT_PROMPT_COMMAND_PALETTE[]; + + +extern char NEWTRODIT_PROMPT_FIRST_FILE_COMPARE[]; +extern char NEWTRODIT_PROMPT_SECOND_FILE_COMPARE[]; + +// Informational dialogs +extern char NEWTRODIT_NO_ERROR[]; +extern char NEWTRODIT_FILE_SAVED[]; +extern char NEWTRODIT_FILE_OPENED[]; +extern char NEWTRODIT_FILE_RELOADED[]; +extern char NEWTRODIT_NEW_FILE_CREATED[]; +extern char NEWTRODIT_FILE_CLOSED[]; +extern char NEWTRODIT_FILE_RENAMED[]; +extern char NEWTRODIT_FILE_WROTE_BUFFER[]; +extern char NEWTRODIT_SETTINGS_RELOADED[]; +extern char NEWTRODIT_SYNTAX_HIGHLIGHTING_LOADED[]; +extern char NEWTRODIT_SYNTAX_HIGHLIGHTING_FAILED[]; +extern char NEWTRODIT_LOCATE_END_REACHED[]; + + +extern char NEWTRODIT_FIND_STRING_NOT_FOUND[]; +extern char NEWTRODIT_FIND_CASE_SENSITIVE[]; +extern char NEWTRODIT_FIND_MATCH_WHOLE_WORD[]; +extern char NEWTRODIT_FIND_NO_MORE_MATCHES[]; + +extern char NEWTRODIT_DEV_TOOLS[]; +extern char NEWTRODIT_TAB_CONVERSION[]; +extern char NEWTRODIT_NULL_CONVERSION[]; +extern char NEWTRODIT_LINE_COUNT[]; +extern char NEWTRODIT_SYNTAX_HIGHLIGHTING[]; +extern char NEWTRODIT_MACRO_SET[]; +extern char NEWTRODIT_OLD_KEYBINDINGS[]; +extern char NEWTRODIT_AUTO_SYNTAX_LOAD[]; +extern char NEWTRODIT_FILE_COMPARE_DIFF[]; +extern char NEWTRODIT_FILE_COMPARE_NODIFF[]; +extern char NEWTRODIT_LOADING_COMPARING_FILES[]; +extern char NEWTRODIT_CURRENT_FILE_SWITCHED[]; +extern char NEWTRODIT_MOUSE[]; + +extern char NEWTRODIT_SHOWING_PREVIOUS_FILE[]; +extern char NEWTRODIT_SHOWING_NEXT_FILE[]; +extern char NEWTRODIT_SWITCHING_FILE[]; +extern char NEWTRODIT_INFO_NO_FILES_TO_SWITCH[]; +extern char NEWTRODIT_CLIPBOARD_COPIED[]; + +// Other dialogs + +extern char NEWTRODIT_DIALOG_BOTTOM_HELP[]; +extern char NEWTRODIT_DIALOG_MANUAL[]; +extern char NEWTRODIT_DIALOG_MANUAL_TITLE[]; +extern char NEWTRODIT_DIALOG_BOTTOM_LOCATE[]; +extern char NEWTRODIT_DIALOG_LOCATE_POS[]; + + +extern char NEWTRODIT_DIALOG_ENABLED[]; +extern char NEWTRODIT_DIALOG_DISABLED[]; +extern char NEWTRODIT_FUNCTION_ABORTED[]; + +// Constants used internally + +extern char NEWTRODIT_MANUAL_MAGIC_NUMBER[]; + +extern char NEWTRODIT_SYNTAX_CAPITAL[]; +extern char NEWTRODIT_SYNTAX_CAPITAL_COLOR[]; +extern char NEWTRODIT_SYNTAX_CAPITAL_MIN[]; +extern char NEWTRODIT_SYNTAX_COMMENT[]; +extern char NEWTRODIT_SYNTAX_COMMENT_COLOR[]; +extern char NEWTRODIT_SYNTAX_DEFAULT_COLOR[]; +extern char NEWTRODIT_SYNTAX_LANGUAGE[]; +extern char NEWTRODIT_SYNTAX_MAGIC_NUMBER[]; +extern char NEWTRODIT_SYNTAX_NUMBER_COLOR[]; +extern char NEWTRODIT_SYNTAX_QUOTE_COLOR[]; +extern char NEWTRODIT_SYNTAX_SEPARATORS[]; +extern char NEWTRODIT_SYNTAX_SINGLE_QUOTES[]; +extern char NEWTRODIT_SYNTAX_FINISH_QUOTES[]; + + +extern char NEWTRODIT_MACRO_CURRENT_FILE[]; +extern char NEWTRODIT_MACRO_FULL_PATH[]; +extern char NEWTRODIT_MACRO_CURRENT_DIR[]; +extern char NEWTRODIT_MACRO_CURRENT_EXTENSION[]; +extern char NEWTRODIT_MACRO_CURRENT_BASENAME[]; +extern char NEWTRODIT_MACRO_CURRENT_DRIVE[]; +extern char NEWTRODIT_SYNTAX_ENCLOSING[]; + +#endif // DIALOG_H \ No newline at end of file diff --git a/src/fileio.c b/src/fileio.c new file mode 100644 index 0000000..a67b50d --- /dev/null +++ b/src/fileio.c @@ -0,0 +1,314 @@ +#include "fileio.h" + +int check_file(char *filename) +{ +#ifdef _WIN32 + return (((_access(filename, 0)) != -1 && (_access(filename, 6)) != -1)) ? 0 : 1; +#else + return 1; +#endif +} + +char *get_full_path(const char *filename) +{ + char *full_path = calloc(MAX_PATH + 1, sizeof(char)); + +#ifdef _WIN32 + if (!_fullpath(full_path, filename, MAX_PATH)) + +#else + full_path = realpath(filename, NULL); + if (!full_path) +#endif + return NULL; + return full_path; +} + +int valid_filename(char *filename) +{ + return strpbrk(filename, "*?\"<>|\x1b") == NULL; +} + +int write_file(File *tstack, FILE *fp) +{ + size_t nl_len = strlen_n(tstack->newline); // Calculate the length just once and not for every iteration + for (size_t i = 1; i <= tstack->linecount; i++) + { + fwrite(tstack->line[i]->str, sizeof(char), tstack->line[i]->len, fp); // DANGER: This approach is dangerous and may lead to buffer overflows + if (i < tstack->linecount) + fwrite(tstack->newline, sizeof(char), nl_len, fp); + } + return 1; +} + +int save_file(File *tstack, char *savefile, bool saveDialog) +{ + char *tmp_filename = calloc(MAX_PATH + 1, sizeof(utf8_int32_t)); + if (savefile && !(tstack->file_flags & IS_UNTITLED)) + utf8cpy(tmp_filename, savefile); + + if (tstack->file_flags & IS_UNTITLED || saveDialog) + { + + print_message(saveDialog ? NEWTRODIT_PROMPT_SAVE_FILE_AS : NEWTRODIT_PROMPT_SAVE_FILE); + fgets(tmp_filename, MAX_PATH * sizeof(utf8_int32_t), stdin); + + tmp_filename[utf8cspn(tmp_filename, "\r\n")] = '\0'; + + if (tmp_filename[0] == '\0') + { + load_all_newtrodit(tstack, NULL); + display_contents(tstack); + function_aborted(tstack, tmp_filename); + return 0; + } + + remove_quotes(tmp_filename); + + if (!valid_file_name(tmp_filename)) + { + load_all_newtrodit(tstack, NULL); + print_message("%s%s", NEWTRODIT_FS_FILE_INVALID_NAME, tmp_filename); + display_contents(tstack); + + getch_n(); + return 0; + } + } + FILE *savefp = fopen(tmp_filename, "wb"); + if (savefp) + { + tstack->fullpath = get_full_path(tmp_filename); + memcpy(tstack->filename, last_token(tmp_filename, PATHDELIMS), MAX_PATH * sizeof(utf8_int32_t)); + } + + if (!savefp) + { + load_all_newtrodit(tstack, NULL); + print_message(NEWTRODIT_FS_FILE_SAVE_ERR, tmp_filename); + display_contents(tstack); + + getch_n(); + return 0; + } + set_status_msg(true, NEWTRODIT_FILE_SAVED); + load_all_newtrodit(tstack, ed.status_msg); + display_contents(tstack); + + write_file(tstack, savefp); + fclose(savefp); + tstack->file_flags &= ~(IS_UNTITLED | IS_MODIFIED); + tstack->file_flags |= IS_SAVED; + tstack->fwrite_time = time(NULL); // Set the write time to the current time + return 0; +} + +void save_modified_file(File *tstack) +{ + if (tstack->file_flags & IS_MODIFIED) + { + print_message("%s", NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE); + if (yes_no_prompt()) + save_file(tstack, NULL, false); + } +} + +int close_file(File *tstack) +{ + save_modified_file(tstack); + print_message("%s", NEWTRODIT_PROMPT_CLOSE_FILE); + if (!yes_no_prompt()) + { + display_status(tstack, ed.status_msg); + return 0; + } + if (ed.open_files > 1) + { + if (free_buffer(&file[ed.file_index])) + { + if (ed.file_index < ed.open_files - 1) // TODO: Fix strsave not copying + { + for (int k = ed.file_index; k < ed.open_files; k++) + memcpy(&file[k], &file[k + 1], sizeof(*file)); + } + ed.open_files--; + + if (ed.file_index > 0) + ed.file_index--; + } + else + { + return 0; + } + } + else + { + if ((tstack->file_flags & IS_UNTITLED && !tstack->file_flags & IS_MODIFIED) || tstack->file_flags & IS_MODIFIED || tstack->file_flags & IS_SAVED || tstack->file_flags & IS_UNTITLED) + { + if (free_buffer(&file[ed.file_index])) + allocate_buffer(&file[ed.file_index]); + } + } + load_all_newtrodit(tstack, NULL); + return 1; +} + +int load_file(File *tstack, FILE *fp) +{ + fseek(fp, 0, SEEK_SET); // Set fp to the beginning of the file pointer + const size_t READ_HEADER = 64; + char *read_header_bom = calloc(READ_HEADER, sizeof(char)); + size_t headerbytesread = fread(read_header_bom, sizeof(char), READ_HEADER, fp); + tstack->encoding = get_file_encoding(read_header_bom, headerbytesread, &tstack->encoding_bom_len); // Identify the encoding of the file being read, assume UTF-8 if none is detected (could this be an issue?) + fseek(fp, 0, SEEK_END); + size_t file_size = ftell(fp); + if (tstack->encoding == ENCODING_UTF8) + { + int text_utf16 = is_utf16((unsigned char *)read_header_bom, headerbytesread, file_size); + switch (text_utf16 + 1) + { + case ENCODING_UTF16LE: + tstack->encoding = ENCODING_UTF16LE; + break; + case ENCODING_UTF16BE: + tstack->encoding = ENCODING_UTF16BE; + break; + default: + tstack->encoding = ENCODING_UTF8; + break; + } + } + + free(read_header_bom); + + fseek(fp, tstack->encoding_bom_len, SEEK_SET); // Start reading the file after the BOM, if present + + char unicode_buffer[5] = {0}; // A Unicode codepoint is max 4 bytes, and make sure to make room for the null terminator + int ch = 0; + size_t xps = 0, uxps = 0, yps = 1, uidx = 0; + size_t nl_len = strlen_n(tstack->newline); + size_t nl_idx = 0; + tstack->linecount = 0; + tstack->size = 0; + bool reading_unicode = false, full_unicode_sequence = false; + const char utf8_replacement_char[3] = {0xef, 0xbf, 0xbd}; + switch (tstack->encoding) + { + case ENCODING_UTF8: + case ENCODING_UTF8BOM: + { + while ((ch = fgetc(fp)) != EOF) + { + if (tstack->linecount == 0) + tstack->linecount++; + if (ch == tstack->newline[nl_idx]) + { + if (++nl_idx >= nl_len) + { + memset(&tstack->line[yps]->str[xps - nl_idx + 1], 0, nl_len - 1); + tstack->linecount++; + tstack->size += nl_len; + xps = 0; + uxps = 0; + yps++; + uidx = 0; + if (yps >= tstack->alloc_lines - 1) + change_allocated_lines(tstack, tstack->alloc_lines, tstack->alloc_lines + LINE_Y_INCREASE); + continue; + } + } + else + { + nl_idx = 0; + } + if (ch < 0x80) + { + if (uidx > 0) + reading_unicode = true; + } + if (ch >= 0x80 || reading_unicode) + { + if (uidx < 3) + unicode_buffer[uidx++] = ch; + else + reading_unicode = true; + + if (!utf8valid(unicode_buffer) || reading_unicode) + { + printf("[%s] (", unicode_buffer); + for (size_t i = 0; i < uidx; i++) + { + printf("%02x ", unicode_buffer[i]); + } + printf(") Is string valid? %s\n", !utf8valid(unicode_buffer) ? "True" : "False"); + if (!utf8valid(unicode_buffer)) + { + memcpy(&tstack->line[yps]->str[xps], unicode_buffer, uidx); + tstack->line[yps]->len += uidx; + xps += uidx; + tstack->size += uidx; + } + else + { + memcpy(&tstack->line[yps]->str[xps], utf8_replacement_char, sizeof(utf8_replacement_char)); // If the UTF-8 sequence is invalid, replace it with Replacement Character + tstack->line[yps]->len += sizeof(utf8_replacement_char); + xps += sizeof(utf8_replacement_char); + tstack->size += sizeof(utf8_replacement_char); + + printf(" invalid unicode! "); + } + + uidx = 0; + memset(unicode_buffer, '\0', 4); + reading_unicode = false; + tstack->line[yps]->ulen++; + reading_unicode = false; + } + } + else + { + tstack->line[yps]->str[xps++] = ch; + printf("{%02x}", ch); + + tstack->line[yps]->len++; + tstack->size++; + tstack->line[yps]->ulen++; + } + } + break; + } + default: + set_status_msg(true, NEWTRODIT_ERROR_UNSUPPORTED_ENCODING); + } + + return 1; +} + +int open_file(File *tstack) +{ + save_modified_file(tstack); + char *tmp_openfile = calloc(MAX_PATH + 1, sizeof(utf8_int32_t)); + print_message(NEWTRODIT_PROMPT_FOPEN); + fgets(tmp_openfile, MAX_PATH * sizeof(utf8_int32_t), stdin); + tmp_openfile[utf8cspn(tmp_openfile, "\r\n")] = '\0'; + + FILE *fp = fopen(tmp_openfile, "rb"); + if (!fp) + { + set_status_msg(false, NEWTRODIT_FS_FILE_OPEN_ERR, tmp_openfile); + load_all_newtrodit(tstack, ed.status_msg); + free(tmp_openfile); + + return 0; + } + + tstack->fullpath = get_full_path(tmp_openfile); + memcpy(tstack->filename, last_token(tmp_openfile, PATHDELIMS), MAX_PATH * sizeof(utf8_int32_t)); + + set_status_msg(true, NEWTRODIT_FILE_OPENED); + load_all_newtrodit(tstack, NULL); + load_file(tstack, fp); + free(tmp_openfile); + + return 1; +} diff --git a/src/fileio.h b/src/fileio.h new file mode 100644 index 0000000..8c43a5d --- /dev/null +++ b/src/fileio.h @@ -0,0 +1,19 @@ +#ifndef FILEIO_H +#define FILEIO_H +#include "newtrodit_core.h" +#include "line.h" +#include "newtrodit_gui.h" +#include "input.h" + + +int check_file(char *filename); +char *get_full_path(const char *filename); +int valid_filename(char *filename); +int write_file(File *tstack, FILE *fp); +int save_file(File *tstack, char *savefile, bool saveDialog); +int load_file(File *tstack, FILE *fp); +int open_file(File *tstack); +int close_file(File *tstack); +void save_modified_file(File *tstack); + +#endif // FILEIO_H \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index 5b7f6ac..2a50aba 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1,818 +1,206 @@ -/* - Newtrodit: A console text editor - Copyright (C) 2021-2023 anic17 Software - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see - -*/ - -const char newtrodit_version[] = "0.6 rc-1"; -const char newtrodit_build_date[] = "27/6/2023"; -const char newtrodit_repository[] = "https://github.com/anic17/Newtrodit"; -const char newtrodit_lcl_repository[] = "https://github.com/anic17/Newtrodit-LCL"; -char manual_file[MAX_PATH] = "newtrodit.man"; -char settings_file[MAX_PATH] = "newtrodit.config"; -char syntax_dir[MAX_PATH] = "syntax"; -char syntax_ext[MAX_PATH] = ".nwtrd-syntax"; -const char newtrodit_commit[] = ""; // Example commit - -const char* autocomplete_double[] = {"{}", "[]", "()", "\"\"", "''"}; - -#define DEFAULT_NL "\n" - -const int MANUAL_BUFFER_X = 300; -const int MANUAL_BUFFER_Y = 600; - -int TAB_WIDE = TAB_WIDE_; -int CURSIZE = CURSIZE_; -int LINECOUNT_WIDE = 4; -int lineCount = true; // Is line count enabled? - -// Boolean settings -int convertTabtoSpaces = true; -int convertNull = true; -int trimLongLines = false; -int cursorSizeInsert = true; -int wrapLine = false; -int autoIndent = true; -int fullPathTitle = true; -int useOldKeybindings = false; // Bool to use old keybinds (^X instead of ^Q, ^K instead of ^X) -int longPositionDisplay = false; -int fullCursorInfoDisplay = true; -int generalUtf8Preference = false; -int partialMouseSupport = true; // Partial mouse support, only changes cursor position when mouse is clicked -int showMillisecondsInTime = false; // Show milliseconds in time insert function (F6) -int useLogFile = true; -int createNewLogFile = false; // Create new log files when logging is enabled -int openFileHandle = true; // Open a file handle -int RGB24bit = false; // Use 24-bit RGB instead of 4-bit colors -int findInsensitive = false; // Find insensitive string -int matchWholeWord = false; // Match whole word when finding a string -int devMode = false; // Bool to enable or disable the dev mode -int autoComplete = true; // Keyword autocompleting (if syntax highlighting is there) - -int bpsPairHighlight = false; // Use BPS pair highlighting (bugged) - -int bps_pair_colors[] = {0x5, 0xb, 0xd, 0x6, 0xc, 0xe, 0x8, 0xf, 0x9}; // Brackets, parenthesis and square brackets colors -char bps_chars_open[10][3] = {"(", "{", "["}; // Allocate 10 chars for each char array to support new syntax -char bps_chars_close[10][3] = {")", "}", "]"}; - -// Interal global variables -int clearAllBuffer = true; -int allowAutomaticResizing = true; - -int c = 0; // Different error/debug codes - -/* - TODO: Add multiline comment support - static int multiLineComment = false; -*/ - - -int syntaxHighlighting = true; -int syntaxAfterDisplay = false; // Display syntax after display -int allocateNewBuffer = true; - -int file_index = 0; -int open_files = 1; - -int wrapSize = 100; // Default wrap size -int scrollRate = 3; // Default scroll rate (scroll 3 lines per mouse wheel). Needs to have partialMouseSupport enabled - -#define BG_DEFAULT 0x07 // Background black, foreground (font) white -#define FG_DEFAULT 0x70 // Background white, foreground (font) black -#define FIND_HIGHLIGHT_COLOR 0xE0 // Background yellow, foreground black - -int bg_color = BG_DEFAULT; // Background color (menu) -int fg_color = FG_DEFAULT; // Foreground color (text) - -char **old_open_files; -int oldFilesIndex = 0; - -char *run_macro, *last_known_exception; - -#define DEFAULT_COMPILER "gcc" -#define DEFAULT_COMPILER_FLAGS "-Wall -O2 -pedantic -std=c99" // Assuming it's GCC and it's C99 compliant - -// Syntax highlighting - -#define DEFAULT_SYNTAX_LANG "C" -#define DEFAULT_SEPARATORS "?!:,()+-/*=~[];{}<> \t&|^." -#define DEFAULT_COMMENTS "//" - -#define DEFAULT_SYNTAX_COLOR 0x7 // White -#define DEFAULT_COMMENT_COLOR 0x8 // Dark grey -#define DEFAULT_QUOTE_COLOR 0xe // Yellow - -#define DEFAULT_NUM_COLOR 0x2 // Dark green -#define DEFAULT_CAPITAL_COLOR 0xc // Red -#define DEFAULT_CAPITAL_MIN_LEN 3 // Highlight capital words that are 3 or more characters long -#define DEFAULT_LINECOUNT_COLOR 0x80 -#define DEFAULT_LINECOUNT_HIGHLIGHT_COLOR 0xf0 - -#define SELECTION_COLOR 0x8 - -#define SEPARATORS DEFAULT_SEPARATORS - -char syntax_separators[512] = SEPARATORS; -char syntax_filename[MAX_PATH] = ""; - -int default_color = DEFAULT_SYNTAX_COLOR; -int comment_color = DEFAULT_COMMENT_COLOR; -int quote_color = DEFAULT_QUOTE_COLOR; -int num_color = DEFAULT_NUM_COLOR; -int capital_color = DEFAULT_CAPITAL_COLOR; -int capital_min_len = DEFAULT_CAPITAL_MIN_LEN; -int linecount_color = DEFAULT_LINECOUNT_COLOR; -int linecount_highlight_color = DEFAULT_LINECOUNT_HIGHLIGHT_COLOR; -int capitalMinEnabled = true; -int singleQuotes = true; -int finishQuotes = true; -int linecountHighlightLine = true; -int autoLoadSyntaxRules = true; - -typedef struct position -{ - int x; - int y; -} position_t; - -typedef struct select_t -{ - position_t start; - position_t end; - bool is_selected; -} select_t; - -typedef struct keywords -{ - char *keyword; - int color; -} keywords_t; - -typedef keywords_t comment_t; -typedef keywords_t enclosing_t; - -keywords_t keywords[] = { - {"break", 5}, - {"continue", 5}, - {"return", 5}, - - {"auto", 9}, - {"const", 9}, - {"volatile", 9}, - {"extern", 9}, - {"static", 9}, - - {"inline", 9}, - {"restrict", 9}, - - {"char", 9}, - {"int", 9}, - {"short", 9}, - {"float", 9}, - {"double", 9}, - {"long", 9}, - {"bool", 9}, - {"void", 9}, - - {"double_t", 0xa}, - {"div_t", 0xa}, - {"float_t", 0xa}, - {"fpos_t", 0xa}, - {"max_align_t", 0xa}, - {"mbstate_t", 0xa}, - {"nullptr_t", 0xa}, - {"ptrdiff_t", 0xa}, - {"sig_atomic_t", 0xa}, - {"size_t", 0xa}, - {"time_t", 0xa}, - {"wchar_t", 0xa}, - {"wint_t", 0xa}, - {"FILE", 0xa}, - - {"uint8_t", 0xa}, - {"uint16_t", 0xa}, - {"uint32_t", 0xa}, - {"uint64_t", 0xa}, - {"uint128_t", 0xa}, - - {"int8_t", 0xa}, - {"int16_t", 0xa}, - {"int32_t", 0xa}, - {"int64_t", 0xa}, - {"int128_t", 0xa}, - - {"struct", 6}, - {"enum", 6}, - {"union", 6}, - {"typedef", 6}, - {"unsigned", 9}, - {"signed", 9}, - {"sizeof", 9}, - {"register", 9}, - - {"do", 6}, - {"if", 6}, - {"else", 6}, - {"while", 6}, - {"switch", 6}, - {"for", 6}, - {"case", 6}, - {"default", 6}, - {"goto", 6}, - - {"#include", 0xb}, - {"#pragma", 0xb}, - {"#define", 0xb}, - {"#ifdef", 0xb}, - {"#undef", 0xb}, - - {"#ifndef", 0xb}, - {"#endif", 0xb}, - {"#if", 0xb}, - {"#else", 0xb}, - {"#elif", 0xb}, - {"#error", 0xb}, - {"#warning", 0xb}, - {"#line", 0xb}, - {"defined", 0xb}, - {"not", 0xb}, - - {"main", 0x6}, - {"WinMain", 0x6}, - - // Macro constants - {"NULL", 0x9}, - {"EOF", 0x9}, - {"WEOF", 0x9}, - {"FILENAME_MAX", 0x9}, - {"WEOF", 0x9}, - {"true", 0x9}, - {"false", 0x9}, - {"errno", 0x9}, - {"stdin", 0x9}, - {"stdout", 0x9}, - {"stderr", 0x9}, - {"__LINE__", 0x9}, - {"__TIME__", 0x9}, - {"__DATE__", 0x9}, - {"__FILE__", 0x9}, - - {"__STDC__", 0x9}, - {"__STDC_VERSION__", 0x9}, - {"__STDC_HOSTED__", 0x9}, - {"__OBJC__", 0x9}, - {"__ASSEMBLER__", 0x9}, - - {"__cplusplus", 0x9}, - - {"WINAPI", 0x6}, - {"APIENTRY", 0x6}, - - {"_stdcall", 0x6}, - {"_CRTIMP", 0x6}, - {"__CRTIMP_INLINE", 0x6}, - - {"__cdecl", 0x6}, - {"__asm__", 0x6}, - {"__volatile__", 0x6}, - {"__attribute__", 0x6}, - {"__AW_SUFFIXED__", 0x6}, - {"__AW_EXTENDED__", 0x6}, - {"__NAME__", 0x6}, - -}; - -comment_t comments[] = { - {"//", 0x8}, - {"/*", 0x8}, - {"*/", 0x8}, -}; - -enclosing_t enclosing[] = { - {"\"", 0xe}, - {"\'", 0xe}, -}; - -typedef struct theme -{ - int bg_color; - int fg_color; - int syntax_color; - int comment_color; - int quote_color; - int num_color; - int capital_color; - int capital_min_len; - int single_quotes; - int capital_min_enabled; - - int linecount_color; -} theme_t; - -theme_t themedark = { - .bg_color = 0, - .fg_color = 0x7, - .syntax_color = 0x7, - .comment_color = 0x8, - .quote_color = 0xe, - .num_color = 0x2, - .capital_color = 0x1, - .capital_min_len = 3, - .single_quotes = false, - .capital_min_enabled = true, - .linecount_color = 0x07, -}; - -theme_t themelight = { - .bg_color = 0x7, - .fg_color = 0x0, - .syntax_color = 0x7, - .comment_color = 0x8, - .quote_color = 0x6, - .num_color = 0x2, - .capital_color = 0x1, - .capital_min_len = 3, - .single_quotes = false, - .capital_min_enabled = true, - .linecount_color = 0x07, -}; - -#define BIT_ESC224 0x80000000 -#define BIT_ESC0 0x40000000 - -/* These four functions are only used on Newtrodit-LCL */ -#define ESC_BITMASK 0x80000000 -#define CTRLCHAR_BITMASK 0x40000000 -#define FKEYS_BITMASK 0x20000000 -#define TILDE_BITMASK 0x10000000 - -enum CONTROL_CODES - -#ifdef _WIN32 -{ - BS = 8, - TAB = 9, - CTRLENTER = 10, - ENTER = 13, - CTRLBS = 127, - ESC = 27, - - UP = 72, - LEFT = 75, - RIGHT = 77, - DOWN = 80, - - ALTUP = 152, - ALTLEFT = 155, - ALTRIGHT = 157, - ALTDOWN = 160, - - HOME = 71, - END = 79, - - CTRLHOME = 119, - CTRLEND = 117, - - INS = 82, - DEL = 83, - - CTRLA = 1, - CTRLB = 2, - CTRLC = 3, - CTRLD = 4, - CTRLE = 5, - CTRLF = 6, - CTRLG = 7, - CTRLH = 8, - CTRLI = 9, - CTRLJ = 10, - CTRLK = 11, - CTRLL = 12, - CTRLM = 13, - CTRLN = 14, - CTRLO = 15, - CTRLP = 16, - CTRLQ = 17, - CTRLR = 18, - CTRLS = 19, - CTRLT = 20, - CTRLU = 21, - CTRLV = 22, - CTRLW = 23, - CTRLX = 24, - CTRLY = 25, - CTRLZ = 26, - - /* Different naming only happens in F11 and F12 */ - F1 = 59, - F2 = 60, - F3 = 61, - F4 = 62, - F5 = 63, - F6 = 64, - F7 = 65, - F8 = 66, - F9 = 67, - F10 = 68, - - F11 = 133, - F12 = 134, - - PGUP = 73, - PGDW = 81, - - CTRLF1 = 94, - CTRLF2 = 95, - CTRLF3 = 96, - CTRLF4 = 97, - CTRLF5 = 98, - CTRLF6 = 99, - CTRLF7 = 100, - CTRLF8 = 101, - CTRLF9 = 102, - CTRLF10 = 103, - CTRLF11 = 137, - CTRLF12 = 138, - - ALTF1 = 104, - ALTF2 = 105, - ALTF3 = 106, - ALTF4 = 107, - ALTF5 = 108, - ALTF6 = 109, - ALTF7 = 110, - ALTF8 = 111, - ALTF9 = 112, - ALTF10 = 113, - ALTF11 = 139, - ALTF12 = 140, - - ALTINS = 162, - ALTHOME = 151, - ALTPGUP = 153, - ALTDEL = 163, - ALTEND = 159, - ALTPGDW = 161, - - SHIFTF1 = 84, - SHIFTF2 = 85, - SHIFTF3 = 86, - SHIFTF4 = 87, - SHIFTF5 = 88, - SHIFTF6 = 89, - SHIFTF7 = 90, - SHIFTF8 = 91, - SHIFTF9 = 92, - SHIFTF10 = 93, - SHIFTF11 = 135, - SHIFTF12 = 136, - - CTRLALTA = 30, - CTRLALTB = 48, - CTRLALTC = 46, - CTRLALTD = 32, - CTRLALTE = 18, // This one actually reports as 63 because on my locale it's the euro symbol. - CTRLALTF = 33, - CTRLALTG = 34, - CTRLALTH = 35, - CTRLALTI = 23, - CTRLALTJ = 36, - CTRLALTK = 37, - CTRLALTL = 38, - CTRLALTM = 50, - CTRLALTN = 49, - CTRLALTO = 24, - CTRLALTP = 25, - CTRLALTQ = 16, - CTRLALTR = 19, - CTRLALTS = 31, - CTRLALTT = 20, - CTRLALTU = 22, - CTRLALTV = 47, - CTRLALTW = 17, - CTRLALTX = 45, - CTRLALTY = 21, - CTRLALTZ = 44, -}; -#else -{ - BS = 127, - TAB = 9, - CTRLENTER = 10, - ENTER = 13, - CTRLBS = 8, - - ESC = 27, - - /* - Used an algorithm in order to encode and fit up to 7 characters into a single int32_t - Assuming characters are all lower than 0x80h (127d) - - The algorithm is works by shifting up to 4 characters 7 bits to the left, using 28 of 32 available bits. - - The last 3 bytes are guaranteed to be boolean data so each character fits in a single bit. - The big endian bit is used to ensure that it is a control keybind starting with ESC. - - - This way we can place 7 characters into 4 bytes, or an int32_t. - - */ - - LEFT = 0xc0002200, - UP = 0xc0002080, - DOWN = 0xc0002100, - RIGHT = 0xc0002180, - - INS = 0xc01f9900, - DEL = 0xc01f9980, - - HOME = 0xc0002400, - END = 0xc0002300, - - PGUP = 0xc01f9a80, - PGDW = 0xc01f9b00, - - SHIFTTAB = 0xc0002d00, - - CTRLA = 1, - CTRLB = 2, - CTRLC = 3, - CTRLD = 4, - CTRLE = 5, - CTRLF = 6, - CTRLG = 7, - CTRLH = 8, - CTRLI = 9, - CTRLJ = 10, - CTRLK = 11, - CTRLL = 12, - CTRLM = 13, - CTRLN = 14, - CTRLO = 15, - CTRLP = 16, - CTRLQ = 17, - CTRLR = 18, - CTRLS = 19, - CTRLT = 20, - CTRLU = 21, - CTRLV = 22, - CTRLW = 23, - CTRLX = 24, - CTRLY = 25, - CTRLZ = 26, - - /* Different naming is used between F1 and F4 (termios-related things) */ - F1 = 0xa0002800, - F2 = 0xa0002880, - F3 = 0xa0002900, - F4 = 0xa0002980, - - F5 = 0xcfcd5880, - F6 = 0xcfcdd880, - F7 = 0xcfce1880, - F8 = 0xcfce5880, - F9 = 0xcfcc1900, - F10 = 0xcfcc5900, - F11 = 0xcfccd900, - F12 = 0xcfcd1900, - - SHIFTF1 = 0xc64ed880, - SHIFTF2 = 0xd64ed880, - SHIFTF3 = 0xe64ed880, - SHIFTF4 = 0xf64ed880, - - SHIFTF5 = 0xf76d5c70, - SHIFTF6 = 0xf76ddc70, - SHIFTF7 = 0xf76e1c70, - SHIFTF8 = 0xf76e5c70, - SHIFTF9 = 0xf76c1cf0, - SHIFTF10 = 0xf76c5cf0, - SHIFTF11 = 0xf76cdcf0, - SHIFTF12 = 0xf76d1cf0, - - CTRLF1 = 0xc6aed880, - CTRLF2 = 0xd6aed880, - CTRLF3 = 0xe6aed880, - CTRLF4 = 0xf6aed880, - CTRLF5 = 0xd76d5c70, - CTRLF6 = 0xd76ddc70, - CTRLF7 = 0xd76e1c70, - CTRLF8 = 0xd76e5c70, - CTRLF9 = 0xd76c1cf0, - CTRLF10 = 0xd76c5cf0, - CTRLF11 = 0xd76cdcf0, - CTRLF12 = 0xd76d1cf0, - - CTRLINS = 0xe6aed900, - CTRLDEL = 0xe6aed980, - CTRLHOME = 0xc6aed880, - CTRLEND = 0xe6aed880, - CTRLPGUP = 0xe6aeda80, - CTRLPGDW = 0xe6aedb00, - - CTRLLEFT = 0xc6aed880, - CTRLUP = 0xd6aed880, - CTRLDOWN = 0xe6aed880, - CTRLRIGHT = 0xf6aed880, - - SHIFTLEFT = 0xc64ed880, - SHIFTUP = 0xd64ed880, - SHIFTDOWN = 0xe64ed880, - SHIFTRIGHT = 0xf64ed880, - - CTRLSHIFTLEFT = 0xc6ced880, - CTRLSHIFTUP = 0xd6ced880, - CTRLSHIFTDOWN = 0xe6ced880, - CTRLSHIFTRIGHT = 0xf6ced880, - - ALTF1 = 0xc66ed880, - ALTF2 = 0xd66ed880, - ALTF3 = 0xe66ed880, - ALTF4 = 0xf66ed880, - ALTF5 = 0xf76d5c70, - ALTF6 = 0xf76ddc70, - ALTF7 = 0xf76e1c70, - ALTF8 = 0xf76e5c70, - ALTF9 = 0xf76c1cf0, - ALTF10 = 0xf76c5cf0, - ALTF11 = 0xf76cdcf0, - ALTF12 = 0xf76d1cf0, - - CTRLALTF1 = 0xc6eed880, - CTRLALTF2 = 0xd6eed880, - CTRLALTF3 = 0xe6eed880, - CTRLALTF4 = 0xf6eed880, - CTRLALTF5 = 0xf76d5c70, - CTRLALTF6 = 0xf76ddc70, - CTRLALTF7 = 0xf76e1c70, - CTRLALTF8 = 0xf76e5c70, - CTRLALTF9 = 0xf76c1cf0, - CTRLALTF10 = 0xf76c5cf0, - CTRLALTF11 = 0xf76cdcf0, - CTRLALTF12 = 0xf76d1cf0, - - CTRLSHIFTF1 = 0xc6ced880, - CTRLSHIFTF2 = 0xd6ced880, - CTRLSHIFTF3 = 0xe6ced880, - CTRLSHIFTF4 = 0xf6ced880, - CTRLSHIFTF5 = 0xf76d5c70, - CTRLSHIFTF6 = 0xf76ddc70, - CTRLSHIFTF7 = 0xf76e1c70, - CTRLSHIFTF8 = 0xf76e5c70, - CTRLSHIFTF9 = 0xf76c1cf0, - CTRLSHIFTF10 = 0xf76c5cf0, - CTRLSHIFTF11 = 0xf76cdcf0, - CTRLSHIFTF12 = 0xf76d1cf0, - - ALTINS = 0xe66ed900, - ALTHOME = 0xc66ed880, - ALTPGUP = 0xe66eda80, - ALTDEL = 0xe66ed980, - ALTEND = 0xe66ed880, - ALTPGDW = 0xe66edb00, - - ALTA = 0xa0184d80, - ALTB = 0xa0188d80, - ALTC = 0xa018cd80, - ALTD = 0xa0190d80, - ALTE = 0xa0194d80, - ALTF = 0xa0198d80, - ALTG = 0xa019cd80, - ALTH = 0xa01a0d80, - ALTI = 0xa01a4d80, - ALTJ = 0xa01a8d80, - ALTK = 0xa01acd80, - ALTL = 0xa01b0d80, - ALTM = 0xa01b4d80, - ALTN = 0xa01b8d80, - ALTO = 0xa01bcd80, - ALTP = 0xa01c0d80, - ALTQ = 0xa01c4d80, - ALTR = 0xa01c8d80, - ALTS = 0xa01ccd80, - ALTT = 0xa01d0d80, - ALTU = 0xa01d4d80, - ALTV = 0xa01d8d80, - ALTW = 0xa01dcd80, - ALTX = 0xa01e0d80, - ALTY = 0xa01e4d80, - ALTZ = 0xa01e8d80, - - CTRLALTA = 0xa0004d80, - CTRLALTB = 0xa0008d80, - CTRLALTC = 0xa000cd80, - CTRLALTD = 0xa0010d80, - CTRLALTE = 0xa0014d80, - CTRLALTF = 0xa0018d80, - CTRLALTG = 0xa001cd80, - CTRLALTH = 0xa0020d80, - CTRLALTI = 0xa0024d80, - CTRLALTJ = 0xa0028d80, - CTRLALTK = 0xa002cd80, - CTRLALTL = 0xa0030d80, - CTRLALTM = 0xa0034d80, - CTRLALTN = 0xa0038d80, - CTRLALTO = 0xa003cd80, - CTRLALTP = 0xa0040d80, - CTRLALTQ = 0xa0044d80, - CTRLALTR = 0xa0048d80, - CTRLALTT = 0xa004cd80, - CTRLALTS = 0xa0050d80, - CTRLALTU = 0xa0054d80, - CTRLALTV = 0xa0058d80, - CTRLALTW = 0xa005cd80, - CTRLALTX = 0xa0060d80, - CTRLALTY = 0xa0064d80, - CTRLALTZ = 0xa0068d80, -}; -#endif - -typedef struct file_t -{ - char *extensions; // Used separator is '|' because for some reason I couldn't get a 2D array working - char *display_name; - size_t extcount; -} file_t; - -char *DEFAULT_LANGUAGE = "File"; - -static file_t FileLang[] = { - {"adb|ads", "Ada", 2}, // Ada - {"awk", "AWK script", 1}, // AWK script - {"bas", "BASIC", 1}, // BASIC - {"bat|cmd|btm", "Batch", 3}, // Batch file - {"bf", "Brainfuck", 1}, // Brainfuck - {"bin|exe|dll|sys|ocx|elf|out", "Binary", 7}, // Binary file - {"c|h|inl", "C", 3}, // C - {"clj|cljs|cljc|edn", "Clojure", 4}, // Clojure - {"config|conf|ini|cfg|cnf|cf", "Configuration", 6}, // Configuration files - {"cpp|hpp|cxx|hxx|c++|h++|cc|hh", "C++", 8}, // C++ file - {"cs|csx", "C#", 2}, // C# - {"css", "CSS styles", 1}, // CSS file - {"csv", "CSV", 1}, // CSV file - {"dart", "Dart", 1}, // Dart - {"docx|docm|pptx|pptm|xlsx|xlsm", "Microsoft Office XML", 6}, // Microsoft Office XML - {"e", "Eiffel", 1}, // Eiffel - {"el|elc|eln", "Emacs Lisp", 3}, // Emacs lisp - {"elm", "Elm", 1}, // ELM - {"erl|hrl", "Erlang", 2}, // Erlang - {"ex|exs", "Elixir", 2}, // Elixir - {"fs|fsi|fsx|fsscript", "F#", 4}, // F# - {"git", "Git", 1}, // Git (not sure if this is an actual file extension) - {"go", "Golang", 1}, // Golang - {"hs|lhs", "Haskell", 1}, // Haskell - {"html|htm", "HTML", 2}, // HTML - {"java|class|jar|jmod", "Java", 4}, // Java - {"jl", "Julia", 1}, // Julia - {"js|cjs|mjs", "JavaScript", 3}, // JavaScript - {"json", "JSON", 1}, // JSON file - {"lua", "Lua", 1}, // Lua - {"m|p|mex|mat|fig|mlx|mlapp|mltbx|mlappinstall|mlpkginstall", "MATLAB", 10}, // MATLAB - {"md|markdown", "Markdown", 2}, // Markdown - {"ml|mli", "OCaml", 2}, // OCaml - {"nb|wl", "Wolfram Language", 2}, // Wolfram Language (Mathematica) - {"newtrodit|nwtrd", "Newtrodit script", 2}, // Newtrodit script - {"nwtrd-syntax", "Newtrodit syntax rules", 1}, // Newtrodit syntax highlighting - {"odt|fodt|ods|fods|odp|fodp|odg|fodg|odf", "OpenDocument", 9}, // OpenDocument - {"pas|pp|inc", "Pascal", 3}, // Pascal - {"pdf", "Portable Document Format", 1}, // Portable Document Format - {"php|phar|phtml|pht|phps", "PHP", 5}, // PHP - {"pl|plx|pm|xs|t|pod|cgi", "Perl", 7}, // Perl - {"ps1|psd1|psm1|ps1xml|pssc|psrc|cdxml", "PowerShell", 7}, // PowerShell script - {"py|pyi|pyc|pyd|pyo|pyw|pyz", "Python", 7}, // Python - {"rb", "Ruby", 1}, // Ruby - {"rs|rlib", "Rust", 2}, // Rust - {"rtf", "Rich Text Format", 1}, // Rich Text Format - {"s|asm|arm", "Assembly", 3}, // Assembly - {"scala|sc", "Scala", 2}, // Scala - {"scm|ss", "Scheme", 2}, // Scheme - {"scss|sass", "SASS styles", 2}, // SASS styles - {"sd7|s7i", "Seed7", 2}, // Seed7 - {"sh|bashrc", "Shell script", 1}, // Shell script - {"sml", "Standard ML", 1}, // Standard ML - {"svg|svgz", "SVG", 2}, // SVG - {"tcl|tbc", "Tcl", 2}, // Tcl - {"tex", "LaTeX", 1}, // LaTeX - {"ts|tsx", "TypeScript", 2}, // TypeScript - {"txt|text|log", "Text", 3}, // Text file - {"vbs|vbe|wsf|wsc|hta|asp", "VBScript", 6}, // VBScript - {"v", "V", 1}, // V language - {"vim|vimrc", "Vim script", 2}, // Vim script - {"vue", "Vue", 1}, // Vue - {"xhtml|xhtm|xht", "XHTML", 2}, // XHTML - {"xml", "XML", 1}, // XML - {"yml|yaml", "YAML", 2}, // YAML -}; \ No newline at end of file +#ifndef GLOBALS_H +#define GLOBALS_H +#ifndef MAX_PATH + #ifdef _WIN32 + #define MAX_PATH 260 + #else + #include + #define MAX_PATH PATH_MAX-1 // Leave one char for null terminator just in case + #endif +#endif + +extern char manual_file[MAX_PATH] = "newtrodit.man"; +extern char settings_file[MAX_PATH] = "newtrodit.config"; +extern char syntax_dir[MAX_PATH] = "syntax"; +extern char syntax_ext[MAX_PATH] = ".nwtrd-syntax"; +extern const char newtrodit_commit[] = ""; // Example commit + +//const char* autocomplete_double[] = {"{}", "[]", "()", "\"\"", "''"}; + +#define DEFAULT_NL "\n" + + +char default_filename[MAX_PATH] = "Untitled"; +char default_language[MAX_PATH] = "File"; +char default_newline[] = "\r\n"; + + +const int MANUAL_BUFFER_X = 300; +const int MANUAL_BUFFER_Y = 600; + +int tab_width = 4; +int line_number_wide = 4; + +// Boolean settings +int postLoadRenderingDefault = true; +int convertTabtoSpaces = true; +int convertCtrlChars = true; +int trimLongLines = false; +int cursorSizeInsert = true; +int wrapLine = false; +int autoIndent = true; +int fullPathTitle = true; +int useOldKeybindings = false; // Bool to use old keybinds (^X instead of ^Q, ^K instead of ^X) +int longPositionDisplay = false; +int fullCursorInfoDisplay = true; +int partialMouseSupport = true; // Partial mouse support, only changes cursor position when mouse is clicked +int showMillisecondsInTime = false; // Show milliseconds in time insert function (F6) +int useLogFile = true; +int createNewLogFile = false; // Create new log files when logging is enabled +int openFileHandle = true; // Open a file handle +int RGB24bit = false; // Use 24-bit RGB instead of 4-bit colors +int findInsensitive = false; // Find insensitive string +int matchWholeWord = false; // Match whole word when finding a string +int devMode = true; // Bool to enable or disable the dev mode +int autoComplete = true; // Keyword autocompleting (if syntax highlighting is there) + +int bpsPairHighlight = false; // Use BPS pair highlighting (bugged) + +int bps_pair_colors[] = {0x5, 0xb, 0xd, 0x6, 0xc, 0xe, 0x8, 0xf, 0x9}; // Brackets, parenthesis and square brackets colors +char bps_chars_open[10][3] = {"(", "{", "["}; // Allocate 10 chars for each char array to support new syntax +char bps_chars_close[10][3] = {")", "}", "]"}; + +// Interal global variables +int clearAllBuffer = true; +int allowAutomaticResizing = true; + +int c = 0; // Different error/debug codes + +/* + TODO: Add multiline comment support + static int multiLineComment = false; +*/ + +int syntaxHighlighting = false; +int syntaxAfterDisplay = false; // Display syntax after display +int allocateNewBuffer = true; +int topBarMenu = false; + +int file_index = 0; +int open_files = 1; + +int wrapSize = 100; // Default wrap size +int scrollRate = 3; // Default scroll rate (scroll 3 lines per mouse wheel). Needs to have partialMouseSupport enabled + + +char **old_open_files; +int oldFilesIndex = 0; + +char *run_macro, *last_known_exception; + +#define DEFAULT_COMPILER "gcc" +#define DEFAULT_COMPILER_FLAGS "-Wall -O2 -pedantic -std=c99" // Assuming it's GCC and it's C99 compliant + +// Syntax highlighting + +#define DEFAULT_SYNTAX_LANG "C" +#define DEFAULT_SEPARATORS "?!:,()+-/*=~[];{}<> \t&|^." +#define DEFAULT_COMMENTS "//" + +#define DEFAULT_SYNTAX_COLOR 0x7 // White +#define DEFAULT_COMMENT_COLOR 0x8 // Dark grey +#define DEFAULT_QUOTE_COLOR 0xe // Yellow + +#define DEFAULT_NUM_COLOR 0x2 // Dark green +#define DEFAULT_CAPITAL_COLOR 0xc // Red +#define DEFAULT_CAPITAL_MIN_LEN 3 // Highlight capital words that are 3 or more characters long +#define DEFAULT_LINECOUNT_COLOR 0x80 +#define DEFAULT_LINECOUNT_HIGHLIGHT_COLOR 0xf0 + +#define SELECTION_COLOR 0x8 + +#define SEPARATORS DEFAULT_SEPARATORS + +char syntax_separators[512] = SEPARATORS; +char syntax_filename[MAX_PATH] = ""; + +int default_color = DEFAULT_SYNTAX_COLOR; +int comment_color = DEFAULT_COMMENT_COLOR; +int quote_color = DEFAULT_QUOTE_COLOR; +int num_color = DEFAULT_NUM_COLOR; +int capital_color = DEFAULT_CAPITAL_COLOR; +int capital_min_len = DEFAULT_CAPITAL_MIN_LEN; +int linecount_color = DEFAULT_LINECOUNT_COLOR; +int linecount_highlight_color = DEFAULT_LINECOUNT_HIGHLIGHT_COLOR; +int capitalMinEnabled = true; +int singleQuotes = true; +int finishQuotes = true; +int linecountHighlightLine = true; +int autoLoadSyntaxRules = true; + + +typedef struct File_lang { + char *ext; + char *language; + size_t extcount; +} File_lang; + + +static File_lang FileLang[] = { + {"adb|ads", "Ada", 2}, // Ada + {"awk", "AWK script", 1}, // AWK script + {"bas", "BASIC", 1}, // BASIC + {"bat|cmd|btm", "Batch", 3}, // Batch file + {"bf", "Brainfuck", 1}, // Brainfuck + {"bin|exe|dll|sys|ocx|elf|out", "Binary", 7}, // Binary file + {"c|h|in", "C", 3}, // C + {"clj|cljs|cljc|edn", "Clojure", 4}, // Clojure + {"config|conf|ini|cfg|cnf|cf", "Configuration", 6}, // Configuration files + {"cpp|hpp|cxx|hxx|c++|h++|cc|hh", "C++", 8}, // C++ file + {"cs|csx", "C#", 2}, // C# + {"css", "CSS styles", 1}, // CSS file + {"csv", "CSV", 1}, // CSV file + {"dart", "Dart", 1}, // Dart + {"docx|docm|pptx|pptm|xlsx|xlsm", "Microsoft Office XM", 6}, // Microsoft Office XML + {"e", "Eiffe", 1}, // Eiffel + {"el|elc|eln", "Emacs Lisp", 3}, // Emacs lisp + {"elm", "Elm", 1}, // ELM + {"erl|hr", "Erlang", 2}, // Erlang + {"ex|exs", "Elixir", 2}, // Elixir + {"fs|fsi|fsx|fsscript", "F#", 4}, // F# + {"git", "Git", 1}, // Git (not sure if this is an actual file extension) + {"go", "Golang", 1}, // Golang + {"hs|lhs", "Haskel", 1}, // Haskell + {"html|htm", "HTM", 2}, // HTML + {"java|class|jar|jmod", "Java", 4}, // Java + {"j", "Julia", 1}, // Julia + {"js|cjs|mjs", "JavaScript", 3}, // JavaScript + {"json", "JSON", 1}, // JSON file + {"lua", "Lua", 1}, // Lua + {"m|p|mex|mat|fig|mlx|mlapp|mltbx|mlappinstall|mlpkginstal", "MATLAB", 10}, // MATLAB + {"md|markdown", "Markdown", 2}, // Markdown + {"ml|mli", "OCam", 2}, // OCaml + {"nb|w", "Wolfram Language", 2}, // Wolfram Language (Mathematica) + {"newtrodit|nwtrd", "Newtrodit script", 2}, // Newtrodit script + {"nwtrd-syntax", "Newtrodit syntax rules", 1}, // Newtrodit syntax highlighting + {"odt|fodt|ods|fods|odp|fodp|odg|fodg|odf", "OpenDocument", 9}, // OpenDocument + {"pas|pp|inc", "Pasca", 3}, // Pascal + {"pdf", "Portable Document Format", 1}, // Portable Document Format + {"php|phar|phtml|pht|phps", "PHP", 5}, // PHP + {"pl|plx|pm|xs|t|pod|cgi", "Per", 7}, // Perl + {"ps1|psd1|psm1|ps1xml|pssc|psrc|cdxm", "PowerShel", 7}, // PowerShell script + {"py|pyi|pyc|pyd|pyo|pyw|pyz", "Python", 7}, // Python + {"rb", "Ruby", 1}, // Ruby + {"rs|rlib", "Rust", 2}, // Rust + {"rtf", "Rich Text Format", 1}, // Rich Text Format + {"s|asm|arm", "Assembly", 3}, // Assembly + {"scala|sc", "Scala", 2}, // Scala + {"scm|ss", "Scheme", 2}, // Scheme + {"scss|sass", "SASS styles", 2}, // SASS styles + {"sd7|s7i", "Seed7", 2}, // Seed7 + {"sh|bashrc", "Shell script", 1}, // Shell script + {"sm", "Standard M", 1}, // Standard ML + {"svg|svgz", "SVG", 2}, // SVG + {"tcl|tbc", "Tc", 2}, // Tcl + {"tex", "LaTeX", 1}, // LaTeX + {"ts|tsx", "TypeScript", 2}, // TypeScript + {"txt|text|log", "Text", 3}, // Text file + {"vbs|vbe|wsf|wsc|hta|asp", "VBScript", 6}, // VBScript + {"v", "V", 1}, // V language + {"vim|vimrc", "Vim script", 2}, // Vim script + {"vue", "Vue", 1}, // Vue + {"xhtml|xhtm|xht", "XHTM", 2}, // XHTML + {"xm", "XM", 1}, // XML + {"yml|yam", "YAM", 2}, // YAML +}; +#endif // GLOBALS_H \ No newline at end of file diff --git a/src/gui.h b/src/gui.h new file mode 100644 index 0000000..e69de29 diff --git a/src/include/tercontrol.h b/src/include/tercontrol.h new file mode 100644 index 0000000..7b6d298 --- /dev/null +++ b/src/include/tercontrol.h @@ -0,0 +1,443 @@ +/********************************************************************************** + + Basic Terminal Control Library + Copyright 2022 Zackery Smith + This library is released under the GPLv3 license + + This library has no dependencies other than the standard C runtime library + +***********************************************************************************/ +#ifndef TC_H +#define TC_H +#endif + +#include +#ifdef _WIN32 +#include +#include // For _getch() function +HANDLE hConsole = INVALID_HANDLE_VALUE, hAlternateScreen = INVALID_HANDLE_VALUE; // WinAPI structures for console +CONSOLE_SCREEN_BUFFER_INFO csbi; +CONSOLE_CURSOR_INFO cci; +#else + +#endif +#include +#include +#include +#include + +#ifdef _WIN32 +#define TC_NRM "" +#else +#define TC_NRM "\x1B[0m" /* Normalize color */ +#endif + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + +// `asprintf` is usable on any POSIX-2008 compliant system (any modern Linux system) +// My compiler likes to complain about it.. Another way to preform this is with `vfprintf` +// Or maybe I'm just a bad programmer :I + +#ifdef _WIN32 + +char *tc_color_id(uint8_t cid, int l) +{ // "l" flag is ignored, just to make it compatible with the POSIX version + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hConsole, (cid / 16) << 4 | (cid % 16)); + return ""; +} + +#else + +char *tc_color_id(uint8_t cid, int l) +{ + + char *esc; + if (!l) + { + asprintf(&esc, "\x1B[48;5;%dm", cid); + } + else + { + asprintf(&esc, "\x1B[38;5;%dm", cid); + } + return esc; +} + +////////////////////////////////////////////////////////////////// +// WARNING: WinAPI doesn't natively support 24-bit colors // +// So this function is not available on Windows systems // +////////////////////////////////////////////////////////////////// +char *tc_rgb(int r, int g, int b, int l) +{ + char *esc; + if (!l) + { + asprintf(&esc, "\x1B[48;2;%d;%d;%dm", r, g, b); + } + else + { + asprintf(&esc, "\x1B[38;2;%d;%d;%dm", r, g, b); + } + return esc; +} +#endif + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +////////////////////////////////////// + +void tc_get_cols_rows(int *cols, int *rows); + +////////////////////////////// +// Common private modes // +////////////////////////////// + +#ifdef _WIN32 + +void tc_hide_cursor() +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + cci.bVisible = FALSE; + SetConsoleCursorInfo(hConsole, &cci); +} +void tc_show_cursor() +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + cci.bVisible = TRUE; + SetConsoleCursorInfo(hConsole, &cci); +} + +void tc_enter_alt_screen() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + hAlternateScreen = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); + SetConsoleActiveScreenBuffer(hAlternateScreen); + SetStdHandle(STD_OUTPUT_HANDLE, hAlternateScreen); +} + +void tc_exit_alt_screen() +{ + if (hConsole == INVALID_HANDLE_VALUE) + { + return; + } + SetConsoleActiveScreenBuffer(hConsole); + CloseHandle(hAlternateScreen); + SetStdHandle(STD_OUTPUT_HANDLE, hConsole); +} + +#else + +#define tc_hide_cursor() puts("\033[?25l") +#define tc_show_cursor() puts("\033[?25h") + +/* These functions don't seem to be doing anything + + #define tc_save_screen() puts("\033[?47h") + #define tc_restore_screen() puts("\033[?47l") + +*/ +#define tc_enter_alt_screen() puts("\033[?1049h\033[H") +#define tc_exit_alt_screen() puts("\033[?1049l") +#endif +////////////////////////////// + +void tc_echo_off(); +void tc_echo_on(); + +void tc_get_cols_rows(int *cols, int *rows) +{ +#ifdef _WIN32 + + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +#else + struct winsize size; + ioctl(1, TIOCGWINSZ, &size); + *cols = size.ws_col; + *rows = size.ws_row; +#endif +} + +#ifdef _WIN32 +void tc_echo_off() // Not intended for user use +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode & (~ENABLE_ECHO_INPUT)); +} +void tc_echo_on() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode | ENABLE_ECHO_INPUT); +} + +void tc_canon_on() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode | ENABLE_ECHO_INPUT); +} + +void tc_canon_off() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD old_mode; + GetConsoleMode(hConsole, &old_mode); + SetConsoleMode(hConsole, old_mode & (~ENABLE_ECHO_INPUT)); +} + +void tc_clear_partial(int x, int y, int width, int height) // Clears a section of the screen +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD count; + COORD homeCoords = {x, y}; + + if (hConsole == INVALID_HANDLE_VALUE) + { + return; + } + + if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) + { + return; + } + for (int i = 0; i < height; i++) + { + FillConsoleOutputCharacter(hConsole, ' ', width, homeCoords, &count); + FillConsoleOutputAttribute(hConsole, csbi.wAttributes, width, homeCoords, &count); + + homeCoords.Y++; + } + homeCoords.Y = y; + SetConsoleCursorPosition(hConsole, homeCoords); + return; +} + +void tc_get_cursor(int *x, int *y) +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + *x = csbi.dwCursorPosition.X; + *y = csbi.dwCursorPosition.Y; +} +void tc_set_cursor(int x, int y) +{ + COORD pos = {x, y}; + SetConsoleCursorPosition(hConsole, pos); +} + +void tc_move_cursor(int x, int y) +{ + GetConsoleScreenBufferInfo(hConsole, &csbi); + int cur_x = csbi.dwCursorPosition.X + x; + int cur_y = csbi.dwCursorPosition.Y + y; + if (cur_x < 0) + { + cur_x = 0; + } + if (cur_y < 0) + { + cur_y = 0; + } + COORD pos = {cur_x, cur_y}; + SetConsoleCursorPosition(hConsole, pos); + +} + +void tc_clear_screen() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + DWORD written; + DWORD bufSize = csbi.dwSize.X * csbi.dwSize.Y; + COORD homeCoords = {0, 0}; // Home coordinates + FillConsoleOutputCharacter(hConsole, ' ', bufSize, homeCoords, &written); +} + +void tc_clear_entire_line() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, csbi.dwCursorPosition.Y, csbi.dwSize.X, 1); +} + +void tc_clear_line_till_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, csbi.dwCursorPosition.Y, csbi.dwCursorPosition.X, 1); +} + +void tc_clear_line_from_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, csbi.dwSize.X, 1); +} + +void tc_clear_from_top_to_cursor() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(0, 0, csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y); +} + +void tc_clear_from_cursor_to_bottom() +{ + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &csbi); + tc_clear_partial(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, csbi.dwSize.X, csbi.dwSize.Y - csbi.dwCursorPosition.Y); +} + +void tc_print(const char *s) +{ + WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), s, strlen(s), NULL, NULL); +} + +int tc_getch() +{ + int ch = _getch(); + if(ch == 0 || ch == 0xE0) + { + ch += 255; + } + return ch; +} + +#else + +#define tc_clear_entire_line() puts("\x1B[2K") +#define tc_clear_line_till_cursor() puts("\x1B[1K") +#define tc_clear_line_from_cursor() puts("\x1B[0K") + +void tc_echo_off() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ECHO; + tcsetattr(1, TCSANOW, &term); +} + +void tc_echo_on() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ECHO; + tcsetattr(1, TCSANOW, &term); +} + +void tc_canon_on() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ICANON; + tcsetattr(1, TCSANOW, &term); +} + +void tc_canon_off() +{ + struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ICANON; + tcsetattr(1, TCSANOW, &term); +} + +void tc_get_cursor(int *X, int *Y) +{ + tc_echo_off(); + tc_canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +#define tc_set_cursor(X, Y) printf("\033[%d;%dH", Y, X) +void tc_move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define tc_clear_screen() puts("\x1B[2J") +#define tc_clear_from_top_to_cursor() puts("\x1B[1J") +#define tc_clear_from_cursor_to_bottom() puts("\x1B[0J") + +void tc_clear_partial(int x, int y, int width, int height) +{ + char *buf = (char *)calloc(width + 1, 1); + memset(buf, 32, width); + tc_set_cursor(x, y); + for (int i = 0; i < height; i++) + { + tc_set_cursor(x, y + i); + fwrite(buf, width, 1, stdout); + } + free(buf); +} + +void tc_print(const char *s) +{ + fprintf(stdout, "%s", s); +} + +int tc_getch() // TODO: Implement this +{ + return 0; +} + +#endif diff --git a/src/include/wcwidth.h b/src/include/wcwidth.h new file mode 100644 index 0000000..3b20f9f --- /dev/null +++ b/src/include/wcwidth.h @@ -0,0 +1,330 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +/* + This library has been slightly modified by anic17 in order to change + the type wchar_t to utf_int32_t, equivalent to int32_t + + While wchar_t is 32 bits in Linux and can represent any Unicode character, + wchar_t is just 16 bits in Windows (defined as a short) thus it cannot + represent correctly any Unicode character as the max value possible is + U+10FFFF, way bigger than U+FFFF (the limit for short data type) + + The following two lines have been added and all the cases where wchar_t + was used was replaced to utf8_int32_t to match the code in utf8.h library + (https://github.com/sheredom/utf8.h) + + */ +#include +typedef int32_t utf8_int32_t; + +struct interval { + int first; + int last; +}; + + + + +/* auxiliary function for binary search in interval table */ +static int bisearch(utf8_int32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that utf8_int32_t characters are encoded + * in ISO 10646. + */ + + +int mk_wcwidth(utf8_int32_t ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +int mk_wcswidth(const utf8_int32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +int mk_wcwidth_cjk(utf8_int32_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +int mk_wcswidth_cjk(const utf8_int32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..dc1f57b --- /dev/null +++ b/src/input.c @@ -0,0 +1 @@ +#include "input.h" \ No newline at end of file diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..b84409c --- /dev/null +++ b/src/input.h @@ -0,0 +1,481 @@ +#ifndef INPUT_H +#define INPUT_H + +#ifdef _WIN32 +#include +#include +#endif +#include "unicode.h" + +#ifndef _O_U16TEXT +#define _O_U16TEXT 0x20000 +#endif + +#define BIT_ESC224 0x80000000 +#define BIT_ESC0 0x40000000 + +#define ALT_BITMASK 0x80000000 +#define CTRL_BITMASK 0x40000000 +#define SHIFT_BITMASK 0x20000000 +#define TILDE_BITMASK 0x10000000 + +typedef struct InputUTF8 +{ + utf8_int32_t utf8char; + unsigned int flags; + char equiv[100]; + size_t inputlen; +} InputUTF8; + +enum CONTROL_CODES +#ifdef _WIN32 // Win32 codes automatically generated by a script +{ + BS = 0x08, + ENTER = 0x0d, + CTRLENTER = 0x0a, + + CTRLA = 0x01, + CTRLB = 0x02, + CTRLC = 0x03, + CTRLD = 0x04, + CTRLE = 0x05, + CTRLF = 0x06, + CTRLG = 0x07, + CTRLH = 0x08, + CTRLI = 0x09, + CTRLJ = 0x0a, + CTRLK = 0x0b, + CTRLL = 0x0c, + CTRLM = 0x0d, + CTRLN = 0x0e, + CTRLO = 0x0f, + CTRLP = 0x10, + CTRLQ = 0x11, + CTRLR = 0x12, + CTRLS = 0x13, + CTRLT = 0x14, + CTRLU = 0x15, + CTRLV = 0x16, + CTRLW = 0x17, + CTRLX = 0x18, + CTRLY = 0x19, + CTRLZ = 0x1a, + ESC = 0x1b, + + F1 = 0x70, + F2 = 0x71, + F3 = 0x72, + F4 = 0x73, + F5 = 0x74, + F6 = 0x75, + F7 = 0x76, + F8 = 0x77, + F9 = 0x78, + F10 = 0x79, + F11 = 0x7a, + F12 = 0x7b, + UP = 0x26, + LEFT = 0x25, + RIGHT = 0x27, + DOWN = 0x28, + INSERT = 0x2d, + HOME = 0x24, + PGUP = 0x21, + PGDW = 0x22, + END = 0x23, + DEL = 0x2e, + CTRLF1 = 0x40000070, + CTRLF2 = 0x40000071, + CTRLF3 = 0x40000072, + CTRLF4 = 0x40000073, + CTRLF5 = 0x40000074, + CTRLF6 = 0x40000075, + CTRLF7 = 0x40000076, + CTRLF8 = 0x40000077, + CTRLF9 = 0x40000078, + CTRLF10 = 0x40000079, + CTRLF11 = 0x4000007a, + CTRLF12 = 0x4000007b, + CTRLUP = 0x40000026, + CTRLLEFT = 0x40000025, + CTRLRIGHT = 0x40000027, + CTRLDOWN = 0x40000028, + CTRLINSERT = 0x4000002d, + CTRLHOME = 0x40000024, + CTRLPGUP = 0x40000021, + CTRLPGDW = 0x40000022, + CTRLEND = 0x40000023, + CTRLDEL = 0x4000002e, + ALTF1 = 0x80000070, + ALTF2 = 0x80000071, + ALTF3 = 0x80000072, + ALTF4 = 0x80000073, + ALTF5 = 0x80000074, + ALTF6 = 0x80000075, + ALTF7 = 0x80000076, + ALTF8 = 0x80000077, + ALTF9 = 0x80000078, + ALTF10 = 0x80000079, + ALTF11 = 0x8000007a, + ALTF12 = 0x8000007b, + ALTUP = 0x80000026, + ALTLEFT = 0x80000025, + ALTRIGHT = 0x80000027, + ALTDOWN = 0x80000028, + ALTINSERT = 0x8000002d, + ALTHOME = 0x80000024, + ALTPGUP = 0x80000021, + ALTPGDW = 0x80000022, + ALTEND = 0x80000023, + ALTDEL = 0x8000002e, + SHIFTF1 = 0x20000070, + SHIFTF2 = 0x20000071, + SHIFTF3 = 0x20000072, + SHIFTF4 = 0x20000073, + SHIFTF5 = 0x20000074, + SHIFTF6 = 0x20000075, + SHIFTF7 = 0x20000076, + SHIFTF8 = 0x20000077, + SHIFTF9 = 0x20000078, + SHIFTF10 = 0x20000079, + SHIFTF11 = 0x2000007a, + SHIFTF12 = 0x2000007b, + SHIFTUP = 0x20000026, + SHIFTLEFT = 0x20000025, + SHIFTRIGHT = 0x20000027, + SHIFTDOWN = 0x20000028, + SHIFTINSERT = 0x2000002d, + SHIFTHOME = 0x20000024, + SHIFTPGUP = 0x20000021, + SHIFTPGDW = 0x20000022, + SHIFTEND = 0x20000023, + SHIFTDEL = 0x2000002e, + CTRLALTF1 = 0xc0000070, + CTRLALTF2 = 0xc0000071, + CTRLALTF3 = 0xc0000072, + CTRLALTF4 = 0xc0000073, + CTRLALTF5 = 0xc0000074, + CTRLALTF6 = 0xc0000075, + CTRLALTF7 = 0xc0000076, + CTRLALTF8 = 0xc0000077, + CTRLALTF9 = 0xc0000078, + CTRLALTF10 = 0xc0000079, + CTRLALTF11 = 0xc000007a, + CTRLALTF12 = 0xc000007b, + CTRLALTUP = 0xc0000026, + CTRLALTLEFT = 0xc0000025, + CTRLALTRIGHT = 0xc0000027, + CTRLALTDOWN = 0xc0000028, + CTRLALTINSERT = 0xc000002d, + CTRLALTHOME = 0xc0000024, + CTRLALTPGUP = 0xc0000021, + CTRLALTPGDW = 0xc0000022, + CTRLALTEND = 0xc0000023, + CTRLALTDEL = 0xc000002e, + CTRLSHIFTF1 = 0x60000070, + CTRLSHIFTF2 = 0x60000071, + CTRLSHIFTF3 = 0x60000072, + CTRLSHIFTF4 = 0x60000073, + CTRLSHIFTF5 = 0x60000074, + CTRLSHIFTF6 = 0x60000075, + CTRLSHIFTF7 = 0x60000076, + CTRLSHIFTF8 = 0x60000077, + CTRLSHIFTF9 = 0x60000078, + CTRLSHIFTF10 = 0x60000079, + CTRLSHIFTF11 = 0x6000007a, + CTRLSHIFTF12 = 0x6000007b, + CTRLSHIFTUP = 0x60000026, + CTRLSHIFTLEFT = 0x60000025, + CTRLSHIFTRIGHT = 0x60000027, + CTRLSHIFTDOWN = 0x60000028, + CTRLSHIFTINSERT = 0x6000002d, + CTRLSHIFTHOME = 0x60000024, + CTRLSHIFTPGUP = 0x60000021, + CTRLSHIFTPGDW = 0x60000022, + CTRLSHIFTEND = 0x60000023, + CTRLSHIFTDEL = 0x6000002e, + ALTSHIFTF1 = 0xa0000070, + ALTSHIFTF2 = 0xa0000071, + ALTSHIFTF3 = 0xa0000072, + ALTSHIFTF4 = 0xa0000073, + ALTSHIFTF5 = 0xa0000074, + ALTSHIFTF6 = 0xa0000075, + ALTSHIFTF7 = 0xa0000076, + ALTSHIFTF8 = 0xa0000077, + ALTSHIFTF9 = 0xa0000078, + ALTSHIFTF10 = 0xa0000079, + ALTSHIFTF11 = 0xa000007a, + ALTSHIFTF12 = 0xa000007b, + ALTSHIFTUP = 0xa0000026, + ALTSHIFTLEFT = 0xa0000025, + ALTSHIFTRIGHT = 0xa0000027, + ALTSHIFTDOWN = 0xa0000028, + ALTSHIFTINSERT = 0xa000002d, + ALTSHIFTHOME = 0xa0000024, + ALTSHIFTPGUP = 0xa0000021, + ALTSHIFTPGDW = 0xa0000022, + ALTSHIFTEND = 0xa0000023, + ALTSHIFTDEL = 0xa000002e, + CTRLALTSHIFTF1 = 0xe0000070, + CTRLALTSHIFTF2 = 0xe0000071, + CTRLALTSHIFTF3 = 0xe0000072, + CTRLALTSHIFTF4 = 0xe0000073, + CTRLALTSHIFTF5 = 0xe0000074, + CTRLALTSHIFTF6 = 0xe0000075, + CTRLALTSHIFTF7 = 0xe0000076, + CTRLALTSHIFTF8 = 0xe0000077, + CTRLALTSHIFTF9 = 0xe0000078, + CTRLALTSHIFTF10 = 0xe0000079, + CTRLALTSHIFTF11 = 0xe000007a, + CTRLALTSHIFTF12 = 0xe000007b, + CTRLALTSHIFTUP = 0xe0000026, + CTRLALTSHIFTLEFT = 0xe0000025, + CTRLALTSHIFTRIGHT = 0xe0000027, + CTRLALTSHIFTDOWN = 0xe0000028, + CTRLALTSHIFTINSERT = 0xe000002d, + CTRLALTSHIFTHOME = 0xe0000024, + CTRLALTSHIFTPGUP = 0xe0000021, + CTRLALTSHIFTPGDW = 0xe0000022, + CTRLALTSHIFTEND = 0xe0000023, + CTRLALTSHIFTDEL = 0xe000002e +}; +#else +{ + BS = 127, + TAB = 9, + CTRLENTER = 10, + ENTER = 13, + CTRLBS = 8, + + ESC = 27, + + /* + Used an algorithm in order to encode and fit up to 7 characters into a single int32_t + Assuming characters are all lower than 0x80h (127d) + + The algorithm is works by shifting up to 4 characters 7 bits to the left, using 28 of 32 available bits. + + The last 3 bytes are guaranteed to be boolean data so each character fits in a single bit. + The big endian bit is used to ensure that it is a control keybind starting with ESC. + + + This way we can place 7 characters into 4 bytes, or an int32_t. + + */ + + LEFT = 0xc0002200, + UP = 0xc0002080, + DOWN = 0xc0002100, + RIGHT = 0xc0002180, + + INS = 0xc01f9900, + DEL = 0xc01f9980, + + HOME = 0xc0002400, + END = 0xc0002300, + + PGUP = 0xc01f9a80, + PGDW = 0xc01f9b00, + + SHIFTTAB = 0xc0002d00, + + CTRLA = 1, + CTRLB = 2, + CTRLC = 3, + CTRLD = 4, + CTRLE = 5, + CTRLF = 6, + CTRLG = 7, + CTRLH = 8, + CTRLI = 9, + CTRLJ = 10, + CTRLK = 11, + CTRLL = 12, + CTRLM = 13, + CTRLN = 14, + CTRLO = 15, + CTRLP = 16, + CTRLQ = 17, + CTRLR = 18, + CTRLS = 19, + CTRLT = 20, + CTRLU = 21, + CTRLV = 22, + CTRLW = 23, + CTRLX = 24, + CTRLY = 25, + CTRLZ = 26, + + /* Different naming is used between F1 and F4 (termios-related things) */ + F1 = 0xa0002800, + F2 = 0xa0002880, + F3 = 0xa0002900, + F4 = 0xa0002980, + + F5 = 0xcfcd5880, + F6 = 0xcfcdd880, + F7 = 0xcfce1880, + F8 = 0xcfce5880, + F9 = 0xcfcc1900, + F10 = 0xcfcc5900, + F11 = 0xcfccd900, + F12 = 0xcfcd1900, + + SHIFTF1 = 0xc64ed880, + SHIFTF2 = 0xd64ed880, + SHIFTF3 = 0xe64ed880, + SHIFTF4 = 0xf64ed880, + + SHIFTF5 = 0xf76d5c70, + SHIFTF6 = 0xf76ddc70, + SHIFTF7 = 0xf76e1c70, + SHIFTF8 = 0xf76e5c70, + SHIFTF9 = 0xf76c1cf0, + SHIFTF10 = 0xf76c5cf0, + SHIFTF11 = 0xf76cdcf0, + SHIFTF12 = 0xf76d1cf0, + + CTRLF1 = 0xc6aed880, + CTRLF2 = 0xd6aed880, + CTRLF3 = 0xe6aed880, + CTRLF4 = 0xf6aed880, + CTRLF5 = 0xd76d5c70, + CTRLF6 = 0xd76ddc70, + CTRLF7 = 0xd76e1c70, + CTRLF8 = 0xd76e5c70, + CTRLF9 = 0xd76c1cf0, + CTRLF10 = 0xd76c5cf0, + CTRLF11 = 0xd76cdcf0, + CTRLF12 = 0xd76d1cf0, + + CTRLINS = 0xe6aed900, + CTRLDEL = 0xe6aed980, + CTRLHOME = 0xc6aed880, + CTRLEND = 0xe6aed880, + CTRLPGUP = 0xe6aeda80, + CTRLPGDW = 0xe6aedb00, + + CTRLLEFT = 0xc6aed880, + CTRLUP = 0xd6aed880, + CTRLDOWN = 0xe6aed880, + CTRLRIGHT = 0xf6aed880, + + SHIFTLEFT = 0xc64ed880, + SHIFTUP = 0xd64ed880, + SHIFTDOWN = 0xe64ed880, + SHIFTRIGHT = 0xf64ed880, + + CTRLSHIFTLEFT = 0xc6ced880, + CTRLSHIFTUP = 0xd6ced880, + CTRLSHIFTDOWN = 0xe6ced880, + CTRLSHIFTRIGHT = 0xf6ced880, + + ALTF1 = 0xc66ed880, + ALTF2 = 0xd66ed880, + ALTF3 = 0xe66ed880, + ALTF4 = 0xf66ed880, + ALTF5 = 0xf76d5c70, + ALTF6 = 0xf76ddc70, + ALTF7 = 0xf76e1c70, + ALTF8 = 0xf76e5c70, + ALTF9 = 0xf76c1cf0, + ALTF10 = 0xf76c5cf0, + ALTF11 = 0xf76cdcf0, + ALTF12 = 0xf76d1cf0, + + CTRLALTF1 = 0xc6eed880, + CTRLALTF2 = 0xd6eed880, + CTRLALTF3 = 0xe6eed880, + CTRLALTF4 = 0xf6eed880, + CTRLALTF5 = 0xf76d5c70, + CTRLALTF6 = 0xf76ddc70, + CTRLALTF7 = 0xf76e1c70, + CTRLALTF8 = 0xf76e5c70, + CTRLALTF9 = 0xf76c1cf0, + CTRLALTF10 = 0xf76c5cf0, + CTRLALTF11 = 0xf76cdcf0, + CTRLALTF12 = 0xf76d1cf0, + + CTRLSHIFTF1 = 0xc6ced880, + CTRLSHIFTF2 = 0xd6ced880, + CTRLSHIFTF3 = 0xe6ced880, + CTRLSHIFTF4 = 0xf6ced880, + CTRLSHIFTF5 = 0xf76d5c70, + CTRLSHIFTF6 = 0xf76ddc70, + CTRLSHIFTF7 = 0xf76e1c70, + CTRLSHIFTF8 = 0xf76e5c70, + CTRLSHIFTF9 = 0xf76c1cf0, + CTRLSHIFTF10 = 0xf76c5cf0, + CTRLSHIFTF11 = 0xf76cdcf0, + CTRLSHIFTF12 = 0xf76d1cf0, + + ALTINS = 0xe66ed900, + ALTHOME = 0xc66ed880, + ALTPGUP = 0xe66eda80, + ALTDEL = 0xe66ed980, + ALTEND = 0xe66ed880, + ALTPGDW = 0xe66edb00, + + ALTA = 0xa0184d80, + ALTB = 0xa0188d80, + ALTC = 0xa018cd80, + ALTD = 0xa0190d80, + ALTE = 0xa0194d80, + ALTF = 0xa0198d80, + ALTG = 0xa019cd80, + ALTH = 0xa01a0d80, + ALTI = 0xa01a4d80, + ALTJ = 0xa01a8d80, + ALTK = 0xa01acd80, + ALTL = 0xa01b0d80, + ALTM = 0xa01b4d80, + ALTN = 0xa01b8d80, + ALTO = 0xa01bcd80, + ALTP = 0xa01c0d80, + ALTQ = 0xa01c4d80, + ALTR = 0xa01c8d80, + ALTS = 0xa01ccd80, + ALTT = 0xa01d0d80, + ALTU = 0xa01d4d80, + ALTV = 0xa01d8d80, + ALTW = 0xa01dcd80, + ALTX = 0xa01e0d80, + ALTY = 0xa01e4d80, + ALTZ = 0xa01e8d80, + + CTRLALTA = 0xa0004d80, + CTRLALTB = 0xa0008d80, + CTRLALTC = 0xa000cd80, + CTRLALTD = 0xa0010d80, + CTRLALTE = 0xa0014d80, + CTRLALTF = 0xa0018d80, + CTRLALTG = 0xa001cd80, + CTRLALTH = 0xa0020d80, + CTRLALTI = 0xa0024d80, + CTRLALTJ = 0xa0028d80, + CTRLALTK = 0xa002cd80, + CTRLALTL = 0xa0030d80, + CTRLALTM = 0xa0034d80, + CTRLALTN = 0xa0038d80, + CTRLALTO = 0xa003cd80, + CTRLALTP = 0xa0040d80, + CTRLALTQ = 0xa0044d80, + CTRLALTR = 0xa0048d80, + CTRLALTT = 0xa004cd80, + CTRLALTS = 0xa0050d80, + CTRLALTU = 0xa0054d80, + CTRLALTV = 0xa0058d80, + CTRLALTW = 0xa005cd80, + CTRLALTX = 0xa0060d80, + CTRLALTY = 0xa0064d80, + CTRLALTZ = 0xa0068d80, +}; +#endif // _WIN32 + +#ifdef _WIN32 +#include "win32/input_win32.h" +#else +#include "linux/input_linux.h" +#endif // _WIN32 + +#endif // INPUT_H \ No newline at end of file diff --git a/src/line.c b/src/line.c new file mode 100644 index 0000000..9547b0c --- /dev/null +++ b/src/line.c @@ -0,0 +1,309 @@ +#include "line.h" +#include "newtrodit_gui.h" + +Line *create_line(File *tstack, size_t ypos) +{ + tstack->line[ypos] = calloc(1, sizeof(Line)); + tstack->line[ypos]->str = calloc(LINE_SIZE, sizeof(utf8_int32_t)); + tstack->line[ypos]->render = calloc(LINE_SIZE, sizeof(utf8_int32_t)); + tstack->line[ypos]->bufx = 10; + tstack->line[ypos]->render_bufx = LINE_SIZE; + tstack->line[ypos]->len = 0; + tstack->line[ypos]->rlen = 0; + tstack->line[ypos]->ulen = 0; + tstack->line[ypos]->rnlen = 0; + tstack->alloc_lines++; + return tstack->line[ypos]; +} + +size_t change_allocated_lines(File *tstack, size_t old_line_count, size_t new_line_count) +{ + Line **linestack = realloc_n(tstack->line, old_line_count * sizeof(Line *), new_line_count * sizeof(Line *)); + if (!linestack) + return old_line_count; + + for (size_t i = old_line_count; i < new_line_count; i++) + create_line(tstack, i); + + tstack->alloc_lines = new_line_count; + return new_line_count; +} + +Line *increase_line(Line *line, size_t increment) +{ + if (!line) + return NULL; + + size_t new_buf_size = line->bufx + increment; + + // Reallocate the line buffer + char *new_str = realloc_n(line->str, line->bufx * sizeof(utf8_int32_t), new_buf_size * sizeof(utf8_int32_t)); + if (!new_str) + return NULL; + + line->str = new_str; + line->bufx = new_buf_size; + + return line; // Success +} + +Line *increase_line_render(Line *line, size_t increment) +{ + if (!line) + return NULL; + + size_t new_buf_size = line->render_bufx + increment; + + char *new_render = realloc_n(line->render, line->render_bufx * sizeof(utf8_int32_t), new_buf_size * sizeof(utf8_int32_t)); + + if (!new_render) + return NULL; + + line->render = new_render; + line->render_bufx = new_buf_size; + + return line; // Success +} + +Line *insert_row(Line **lines, size_t startpos, size_t arrsize, Line *arrvalue) +{ + if (startpos > arrsize) + return NULL; + memmove(lines + startpos + 1, lines + startpos, (arrsize - startpos) * sizeof(Line *)); + // lines[startpos] = arrvalue; + return lines[startpos]; +} + +Line *delete_row(Line **lines, size_t startpos, size_t arrsize) +{ + for (size_t i = startpos; i < arrsize; i++) + lines[i] = lines[i + 1]; + + return lines[startpos]; +} +/* +char *render_line(File *tstack, size_t ypos) +{ + memcpy(tstack->line[ypos]->render, tstack->line[ypos]->str, ed.xsize - tstack->linenumber_wide - tstack->linenumber_padding - 1); // TODO: Temporary render fix, apply relative ypos and xpos + tstack->line[ypos]->rlen = strlen_n(tstack->line[ypos]->render); + + if (tstack->line[ypos]->rlen < ed.xsize - tstack->linenumber_wide - tstack->linenumber_padding - 1) + memset(tstack->line[ypos]->render + tstack->line[ypos]->rlen, ' ', (ed.xsize - tstack->linenumber_wide - tstack->linenumber_padding - 1) - tstack->line[ypos]->rlen); + + tstack->line[ypos]->rlen = strlen_n(tstack->line[ypos]->render); + return tstack->line[ypos]->render; +} + */ +char *render_line(File *tstack, size_t yps) +{ + size_t len = tstack->line[yps]->len; // Original line length in bytes + size_t rlen = 0; // Render length in bytes + size_t ulen = 0; // Number of characters rendered (not bytes) + size_t uwlen = 0; // Number of Unicode characters rendered (taking into account the different widths) + // size_t old_rnlen = tstack->line[yps]->rnlen; + size_t ulen_increase = 0; // Bytes to advance for the next Unicode character + + char *s_ptr = tstack->line[yps]->str; // Pointer to the start of the line string + utf8_int32_t null_symbol = 0xe29080; // Symbol to represent control characters + utf8_int32_t out_codepoint = 0; // Variable to hold the decoded codepoint + size_t lnum = (ed.lineNumbers ? (tstack->linenumber_wide + tstack->linenumber_padding + 1) : 0); + + size_t display_uchars = len; // Characters to display on the screen + if (display_uchars > ed.xsize - lnum) + display_uchars = ed.xsize - lnum; + + while (uwlen < display_uchars && *s_ptr != '\0') + { + // Decode the next codepoint and get the byte length + char *next_ptr = utf8codepoint(s_ptr, &out_codepoint); + size_t byte_len = next_ptr - s_ptr; + uwlen += mk_wcwidth(out_codepoint); // Increase the Unicode width counter + + // Increase the Unicode length counter + ulen++; + + // If the render buffer is not big enough, increase it + if (rlen + LINE_SIZE > tstack->line[yps]->render_bufx) + increase_line_render(tstack->line[yps], LINE_SIZE); + + if (convertCtrlChars && out_codepoint < 0x20) + { + // Convert control characters to a visible representation + utf8catcodepoint(tstack->line[yps]->render + rlen, null_symbol + out_codepoint, tstack->line[yps]->render_bufx - rlen); + rlen += utf8codepointsize(null_symbol + out_codepoint); // Correctly calculate size of null_symbol + } + else + { + // Copy the current UTF-8 sequence to the render buffer + memcpy(tstack->line[yps]->render + rlen, s_ptr, byte_len); + rlen += byte_len; + } + + // Move to the next character + s_ptr = next_ptr; + } + // Fill the rest of the line with spaces if needed + tstack->line[yps]->rnlen = rlen; + if (uwlen < ed.xsize - lnum) + { + /* printf("uwlen: %zu, rlen: %zu, lnum: %zu, yps: %zu. Filling: %zu\n", uwlen, rlen, lnum, yps, ed.xsize - lnum - uwlen); + getch(); */ + /* printf("rlen: %zu\n", rlen); + getch(); */ + + memset(&tstack->line[yps]->render[rlen], ' ', ed.xsize - lnum - uwlen); + rlen = ed.xsize - lnum; + + } + + // Null-terminate the rendered string + tstack->line[yps]->render[rlen] = '\0'; + tstack->line[yps]->rlen = rlen; // Store the length of the rendered line + + return tstack->line[yps]->render; +} + +void see_buffer(Line **lines, size_t linecount) +{ + for (size_t i = 1; i <= linecount; i++) + printf("str[%zu]=%s\n", i, lines[i]->str); +} + +void see_render_buffer(Line **lines, size_t linecount) +{ + for (size_t i = 1; i <= linecount; i++) + printf("rdr[%zu]=%s\n", i, lines[i]->render); +} + +int split_row(File *tstack, size_t split_x, size_t *yps) +{ + + // Split a line in position split_x into a new row like the Enter key is pressed in the middle of a line + + Line *curr_line = tstack->line[*yps]; + + size_t len = curr_line->len - split_x; + + tstack->linecount++; + + insert_row(tstack->line, *yps, tstack->linecount, NULL); + create_line(tstack, (*yps) + 1); + tstack->alloc_lines++; + + // Copy all characters starting from xpos from the old line to the new line + + memcpy(tstack->line[(*yps) + 1]->str, &curr_line->str[split_x], curr_line->len - split_x); + memset(&curr_line->str[split_x], 0, curr_line->bufx - split_x); + tstack->line[(*yps) + 1]->len = len; + curr_line->len = split_x; + // We cannot use strlen, strlen_n, utf8len or utf8len_n here because string are not null-terminated + + curr_line->ulen = utf8len_null(curr_line->str, curr_line->len); + tstack->line[(*yps) + 1]->ulen = utf8len_null(tstack->line[(*yps) + 1]->str, tstack->line[(*yps) + 1]->len); + + tstack->xpos = 0; + tstack->uxpos = 0; + tstack->uwxpos = 0; +/* see_buffer(tstack->line, tstack->linecount); + getch(); */ + + render_line(tstack, *yps); + render_line(tstack, ++(*yps)); +/* see_render_buffer(tstack->line, tstack->linecount); + getch(); */ + // see_render_buffer(tstack->line, tstack->linecount); + tstack->ypos = *yps; + + increase_scroll(tstack, 1); + return *yps; +} + +int display_contents(File *tstack) +{ + size_t disp_lines = tstack->linecount - tstack->begin_display.y; + for (size_t i = tstack->begin_display.y; i <= disp_lines; i++) + { + if (tstack->post_load_rendering) + render_line(tstack, i); + + display_line(tstack, i, tstack->line[i]->rlen); + } + return 0; +} + + +int allocate_buffer(File **tstack) +{ + *tstack = calloc(1, sizeof(File)); + (*tstack)->file_flags = IS_UNTITLED; + + (*tstack)->filename = calloc(MAX_PATH * sizeof(utf8_int32_t) + 1, sizeof(char)); + memcpy((*tstack)->filename, default_filename, MAX_PATH); + + (*tstack)->fwrite_time = time(NULL); + (*tstack)->fread_time = time(NULL); + (*tstack)->language = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); + memcpy((*tstack)->language, default_language, utf8len_n(default_language)); + (*tstack)->linenumber_wide = 3; + (*tstack)->linenumber_padding = 1; + (*tstack)->linecount = 0; + (*tstack)->xpos = 0; + (*tstack)->ypos = 1; + (*tstack)->size = 0; + (*tstack)->encoding = ENCODING_UTF8; + (*tstack)->encoding_bom_len = 0; + (*tstack)->post_load_rendering = true; + + (*tstack)->line = calloc(DEFAULT_ALLOC_LINES, sizeof(Line *)); + + for (size_t i = 0; i <= DEFAULT_ALLOC_LINES; i++) + create_line(*tstack, i); + + (*tstack)->line[0]->str = " Illegal row number. Report this issue to the GitHub repository."; // If for some reason line 0 is accessed + + (*tstack)->newline = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); + memcpy((*tstack)->newline, default_newline, utf8len_n(default_newline)); + + return 1; +} + +int free_buffer(File **tstack) +{ + + for (size_t i = 1; i < (*tstack)->alloc_lines; i++) + { + free((*tstack)->line[i]->render); + free((*tstack)->line[i]->str); + free((*tstack)->line[i]); + } + + // free((*tstack)->line); + + free((*tstack)->newline); + free((*tstack)->filename); + free((*tstack)->language); + + /* free((*tstack)->compilerinfo.path); + free((*tstack)->compilerinfo.flags); + free((*tstack)->compilerinfo.output); + + free((*tstack)->syntaxinfo.syntax_lang); + free((*tstack)->syntaxinfo.syntax_file); + free((*tstack)->syntaxinfo.separators); + free((*tstack)->syntaxinfo.comments); + + for (int i = 0; i < (*tstack)->syntaxinfo.keyword_count; i++) + { + free((*tstack)->syntaxinfo.keywords[i]); + } + + for (int i = 0; i < (*tstack)->syntaxinfo.comment_count; i++) + { + free((*tstack)->syntaxinfo.comments[i]); + } + + free((*tstack)->syntaxinfo.keywords); + free((*tstack)->syntaxinfo.color); + free((*tstack)->syntaxinfo.comments); */ + return 1; +} \ No newline at end of file diff --git a/src/line.h b/src/line.h new file mode 100644 index 0000000..905db94 --- /dev/null +++ b/src/line.h @@ -0,0 +1,19 @@ + +#ifndef LINE_H +#define LINE_H +#include "newtrodit_core.h" + +Line *create_line(File *tstack, size_t ypos); +size_t change_allocated_lines(File *tstack, size_t old_line_count, size_t new_line_count); +Line *increase_line(Line *line, size_t increment); +Line *increase_line_render(Line *line, size_t increment); +Line *insert_row(Line **lines, size_t startpos, size_t arrsize, Line *arrvalue); +Line *delete_row(Line **lines, size_t startpos, size_t arrsize); +char *render_line(File *tstack, size_t yps); +void see_buffer(Line **lines, size_t linecount); +void see_render_buffer(Line **lines, size_t linecount); +int split_row(File *tstack, size_t split_x, size_t *yps); +int display_contents(File *tstack); +int allocate_buffer(File **tstack); +int free_buffer(File **tstack); +#endif \ No newline at end of file diff --git a/src/linux/graphics_linux.c b/src/linux/graphics_linux.c new file mode 100644 index 0000000..e03ef64 --- /dev/null +++ b/src/linux/graphics_linux.c @@ -0,0 +1,168 @@ +#include +#include +#include + +#define TC_NRM "\x1B[0m" /* Normalize color */ + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + + +#define clear_entire_line() puts("\x1B[2K") +#define clear_line_till_cursor() puts("\x1B[1K") +#define clear_line_from_cursor() puts("\x1B[0K") + + +void color_id(uint8_t cid, int l) +{ + printf((l) ? "\x1B[38;5;%dm" : "\x1B[48;5;%dm", cid); +} + +void set_color(Color color) +{ + if (color.background) + { + printf("\x1B[48;2;%d;%d;%dm", color.r, color.g, color.b); + } + else + { + printf("\x1B[38;2;%d;%d;%dm", color.r, color.g, color.b); + } +} + +void rgb(int r, int g, int b, int l) +{ + printf((l) ? "\x1B[38;5;%d;%d;%dm" : "\x1B[48;5;%d;%d;%dm", r, g, b); +} + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +void echo_off() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ECHO; + tcsetattr(1, TCSANOW, &term); */ +} + +void echo_on() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ECHO; + tcsetattr(1, TCSANOW, &term); */ +} + +void canon_on() +{ +/* struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ICANON; + tcsetattr(1, TCSANOW, &term); + */ +} +void canon_off() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ICANON; + tcsetattr(1, TCSANOW, &term); */ +} + +void get_cursor(int *X, int *Y) +{ + echo_off(); + canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +void gotoxy(int x, int y) +{ + printf("\033[%d;%df", y+1, x+1); +} + +void move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define clear_screen() puts("\x1B[2J") +#define clear_from_top_to_cursor() puts("\x1B[1J") +#define clear_from_cursor_to_bottom() puts("\x1B[0J") + +void clear_partial(int x, int y, int width, int height) +{ + char *buf = calloc(width + 1, 1); + memset(buf, 32, width); + gotoxy(x, y); + for (int i = 0; i < height; i++) + { + gotoxy(x, y + i); + fwrite(buf, width, 1, stdout); + } + gotoxy(x, y); + free(buf); +} + +void get_cols_rows(size_t *cols, size_t *rows) +{ + struct winsize size; + ioctl(1, TIOCGWINSZ, &size); + *cols = (size_t) size.ws_col; + *rows = (size_t) size.ws_row; +} + +int alternate_buffer(bool enabled) +{ + fputs(enabled ? "\033[?1049h" : "\033[?1049l", stdout); + return enabled; +} \ No newline at end of file diff --git a/src/linux/graphics_linux.h b/src/linux/graphics_linux.h new file mode 100644 index 0000000..e03ef64 --- /dev/null +++ b/src/linux/graphics_linux.h @@ -0,0 +1,168 @@ +#include +#include +#include + +#define TC_NRM "\x1B[0m" /* Normalize color */ + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + + +#define clear_entire_line() puts("\x1B[2K") +#define clear_line_till_cursor() puts("\x1B[1K") +#define clear_line_from_cursor() puts("\x1B[0K") + + +void color_id(uint8_t cid, int l) +{ + printf((l) ? "\x1B[38;5;%dm" : "\x1B[48;5;%dm", cid); +} + +void set_color(Color color) +{ + if (color.background) + { + printf("\x1B[48;2;%d;%d;%dm", color.r, color.g, color.b); + } + else + { + printf("\x1B[38;2;%d;%d;%dm", color.r, color.g, color.b); + } +} + +void rgb(int r, int g, int b, int l) +{ + printf((l) ? "\x1B[38;5;%d;%d;%dm" : "\x1B[48;5;%d;%d;%dm", r, g, b); +} + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +void echo_off() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ECHO; + tcsetattr(1, TCSANOW, &term); */ +} + +void echo_on() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ECHO; + tcsetattr(1, TCSANOW, &term); */ +} + +void canon_on() +{ +/* struct termios term; + tcgetattr(1, &term); + term.c_lflag |= ICANON; + tcsetattr(1, TCSANOW, &term); + */ +} +void canon_off() +{ + /* struct termios term; + tcgetattr(1, &term); + term.c_lflag &= ~ICANON; + tcsetattr(1, TCSANOW, &term); */ +} + +void get_cursor(int *X, int *Y) +{ + echo_off(); + canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +void gotoxy(int x, int y) +{ + printf("\033[%d;%df", y+1, x+1); +} + +void move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define clear_screen() puts("\x1B[2J") +#define clear_from_top_to_cursor() puts("\x1B[1J") +#define clear_from_cursor_to_bottom() puts("\x1B[0J") + +void clear_partial(int x, int y, int width, int height) +{ + char *buf = calloc(width + 1, 1); + memset(buf, 32, width); + gotoxy(x, y); + for (int i = 0; i < height; i++) + { + gotoxy(x, y + i); + fwrite(buf, width, 1, stdout); + } + gotoxy(x, y); + free(buf); +} + +void get_cols_rows(size_t *cols, size_t *rows) +{ + struct winsize size; + ioctl(1, TIOCGWINSZ, &size); + *cols = (size_t) size.ws_col; + *rows = (size_t) size.ws_row; +} + +int alternate_buffer(bool enabled) +{ + fputs(enabled ? "\033[?1049h" : "\033[?1049l", stdout); + return enabled; +} \ No newline at end of file diff --git a/src/linux/input_linux.c b/src/linux/input_linux.c new file mode 100644 index 0000000..f7a9b34 --- /dev/null +++ b/src/linux/input_linux.c @@ -0,0 +1,110 @@ +struct termios orig_termios; + +int raw_mode(int fd) +{ + /* This is taken from kilo, make sure to check the project at https://github.com/antirez/kilo */ + struct termios raw; + + if (!isatty(STDIN_FILENO)) + { + perror("raw_mode()"); + return 0; + } + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. */ + raw.c_cc[VMIN] = 0; /* Return each byte, or zero for timeout. */ + raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */ + if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) + { + perror("tcsetattr()"); + return 0; + } + return 1; +} + +void disable_raw_mode(int fd) +{ + /* Don't even check the return value as it's too late. */ + tcsetattr(fd, TCSAFLUSH, &orig_termios); +} + +InputUTF8 get_newtrodit_input() +{ + InputUTF8 inputchar = {0}; + + if (!raw_mode(STDIN_FILENO)) + { + return inputchar; + } + const size_t maxreadsz = 1024; + unsigned char *buf = calloc(sizeof(char), maxreadsz + 1), seq[100] = {0}; // Have enough space for all the buffer + size_t numread; + + numread = read(STDIN_FILENO, buf, maxreadsz); + if (buf[0] == 27) + { + if (numread > 1) // Control code + { + // Key encoding starts here + if ((buf[1] == '[' || buf[1] == 'O')) // -30 is signed 226 + { + if (buf[2] == 226) // Special case for Euro symbol in european layouts + { + buf[2] = 'E'; + } + if (numread > 7) + numread = 7; + inputchar.flags |= 0xFF; + for (size_t i = 2; i < numread; i++) + { + inputchar.flags = inputchar.flags << 8; // Shift only seven bytes + inputchar.flags |= buf[i]; + } + inputchar.inputlen = numread; + } + else + { + inputchar.flags = buf[1]; // Alt-[key] special case + inputchar.inputlen = 2; + } + } + } + + else + { + if (numread > 4) + numread = 4; + inputchar.inputlen = numread; + + for (int i = 0; i < numread; i++) + { + inputchar.utf8char = inputchar.utf8char << 8; + inputchar.utf8char |= buf[i]; + inputchar.equiv[i] = buf[i]; + } + } + /* if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) < 0) // Restore original mode + { + perror("Failed to restore old termios mode!"); + } */ + free(buf); + disable_raw_mode(STDIN_FILENO); + return inputchar; +} + +int getch_n() +{ + InputUTF8 ret = get_newtrodit_input(); + return ret.utf8char; +} diff --git a/src/linux/input_linux.h b/src/linux/input_linux.h new file mode 100644 index 0000000..f7a9b34 --- /dev/null +++ b/src/linux/input_linux.h @@ -0,0 +1,110 @@ +struct termios orig_termios; + +int raw_mode(int fd) +{ + /* This is taken from kilo, make sure to check the project at https://github.com/antirez/kilo */ + struct termios raw; + + if (!isatty(STDIN_FILENO)) + { + perror("raw_mode()"); + return 0; + } + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. */ + raw.c_cc[VMIN] = 0; /* Return each byte, or zero for timeout. */ + raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */ + if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) + { + perror("tcsetattr()"); + return 0; + } + return 1; +} + +void disable_raw_mode(int fd) +{ + /* Don't even check the return value as it's too late. */ + tcsetattr(fd, TCSAFLUSH, &orig_termios); +} + +InputUTF8 get_newtrodit_input() +{ + InputUTF8 inputchar = {0}; + + if (!raw_mode(STDIN_FILENO)) + { + return inputchar; + } + const size_t maxreadsz = 1024; + unsigned char *buf = calloc(sizeof(char), maxreadsz + 1), seq[100] = {0}; // Have enough space for all the buffer + size_t numread; + + numread = read(STDIN_FILENO, buf, maxreadsz); + if (buf[0] == 27) + { + if (numread > 1) // Control code + { + // Key encoding starts here + if ((buf[1] == '[' || buf[1] == 'O')) // -30 is signed 226 + { + if (buf[2] == 226) // Special case for Euro symbol in european layouts + { + buf[2] = 'E'; + } + if (numread > 7) + numread = 7; + inputchar.flags |= 0xFF; + for (size_t i = 2; i < numread; i++) + { + inputchar.flags = inputchar.flags << 8; // Shift only seven bytes + inputchar.flags |= buf[i]; + } + inputchar.inputlen = numread; + } + else + { + inputchar.flags = buf[1]; // Alt-[key] special case + inputchar.inputlen = 2; + } + } + } + + else + { + if (numread > 4) + numread = 4; + inputchar.inputlen = numread; + + for (int i = 0; i < numread; i++) + { + inputchar.utf8char = inputchar.utf8char << 8; + inputchar.utf8char |= buf[i]; + inputchar.equiv[i] = buf[i]; + } + } + /* if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) < 0) // Restore original mode + { + perror("Failed to restore old termios mode!"); + } */ + free(buf); + disable_raw_mode(STDIN_FILENO); + return inputchar; +} + +int getch_n() +{ + InputUTF8 ret = get_newtrodit_input(); + return ret.utf8char; +} diff --git a/src/newtrodit.c b/src/newtrodit.c index f85d7c9..c4c2ad1 100644 --- a/src/newtrodit.c +++ b/src/newtrodit.c @@ -1,2142 +1,381 @@ -/* - Newtrodit: A console text editor - Copyright (c) 2021-2023 anic17 Software - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. +#include "dialog.h" +#include "globals.h" +#include "newtrodit.h" - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +void sigsegv_handler() +{ + /* signal(SIGSEGV, sigsegv_handler); + alternate_buffer(false); + fprintf(stderr, "Fatal Newtrodit exception! (segmentation violation)\nReport this issue to the project's GitHub."); + fflush(stdout); + exit(errno); */ +} + +int init_editor(int argc, char* argv[]) +{ - You should have received a copy of the GNU General Public License - along with this program. If not, see -*/ +#ifdef WIN32 -/* + if (!_isatty(_fileno(stdout)) || !_isatty(_fileno(stderr))) +#else + if (!isatty(STDOUT_FILENO)) +#endif + { + /* + If for some reason stdout or stderr are still redirected to a file, show an error and quit + */ + fprintf(stderr, "%s\n", NEWTRODIT_ERROR_REDIRECTED_TTY); + exit(1); + } - Some remarks about the code: - - 'c' is used for various purposes, and always has a negative value which means a the code of a key. - - 'ch' is the character typed by the user. - - 'Tab_stack[file_index].strsave' is the buffer where the file is stored on RAM. + signal(SIGSEGV, sigsegv_handler); + signal(SIGINT, NULL); + ed.open_files = 1; + ed.file_index = 0; + ed.log_file_name = NULL; + + ed.useLogFile = false; + ed.lineNumbers = true; + ed.dirty = false; + ed.displayStatusOnce = false; + // ed.status_msg = calloc(DEFAULT_ALLOC_SIZE, sizeof(utf8_int32_t)); + ed.status_msg = NULL; + file = calloc(open_files, sizeof(File *)); - The source code of Newtrodit is composed by: +#ifdef _WIN32 + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); - dialog.h : Dialogs - globals.h : Global variables - manual.c : Manual - newtrodit.c : Main source file - newtrodit_core.h : All core functions - ~ win32/newtrodit_core_win.h - ~ linux/newtrodit_core_linux.h - newtrodit_func.c : Main functions - ~ win32/newtrodit_func_win.c - ~ linux/newtrodit_func_linux.c - newtrodit_gui.c : GUI loading - newtrodit_shared.c : Shared functions - newtrodit_locate.c : File explorer - newtrodit_syntax.h : Syntax highlighting + SetConsoleCtrlHandler(NULL, TRUE); + DWORD dwState = 0; - See 'newtrodit --help' + // GetConsoleMode(hStdin, &dwState); + // SetConsoleMode(hStdin, dwState); -*/ + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); +#else + if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) + fprintf(stderr, "%s", NEWTRODIT_ERROR_FAILED_CONSOLE_ATTRIB); + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + orig_termios.c_iflag |= BRKINT | ICRNL | INPCK | ISTRIP | IXON; + /* output modes - disable post processing */ + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + orig_termios.c_cflag &= ~(CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + orig_termios.c_lflag |= ECHO | ICANON | IEXTEN | ISIG; +#endif -#include -#include -#include -#include -#include -#include "manual.c" -#include + get_cols_rows(&ed.xsize, &ed.ysize); + vt_settings(true); + // alternate_buffer(true); -void sigsegv_handler(int signum) -{ - signal(SIGSEGV, sigsegv_handler); - NewtroditCrash(join("Segmentation fault.\nSignum code: ", itoa_n(signum)), errno); - fflush(stdout); - exit(errno); -} + allocate_buffer(&file[ed.file_index]); // Pass the address of the file pointer + load_all_newtrodit(file[ed.file_index], NULL); -void sigtrap_handler(int signum) -{ - signal(SIGTRAP, sigtrap_handler); - NewtroditCrash(join("Sigtrap.\nSignum code: ", itoa_n(signum)), errno); - fflush(stdout); - exit(errno); + return 1; } -void sigabrt_handler(int signum) -{ - signal(SIGABRT, sigabrt_handler); - NewtroditCrash(join("Abort signal.\nSignum code: ", itoa_n(signum)), errno); - fflush(stdout); - exit(errno); -} -void sigfpe_handler(int signum) +int end_editor() { - signal(SIGFPE, sigabrt_handler); - NewtroditCrash(join("Arithmetic exception.\nSignum code: ", itoa_n(signum)), errno); - fflush(stdout); - exit(errno); + alternate_buffer(false); + echo_on(); + canon_on(); + return 1; } -int LoadSettings(char *newtrodit_config_file, char *macro, int *sigsegv, File_info *tstack) +void quit_newtrodit(File *tstack) { - /* - Settings are stored in an INI-like format. - The format is: - key=value - ;comment - - */ - - chdir(SInf.location); - - WriteLogFile("Loading settings file: %s", FullPath(newtrodit_config_file)); - - FILE *settings = fopen(newtrodit_config_file, "rb"); - if (!settings) + if (tstack->file_flags & IS_MODIFIED) { - return 0; + print_message(NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE); + if (yes_no_prompt()) + save_file(tstack, tstack->filename, !!(tstack->file_flags & IS_UNTITLED)); // tstack->file_flags &~ IS_SAVED isn't needed due to being redundant } - - char setting_buf[1024]; // Max 1 kB per line - char *iniptr = malloc(sizeof(setting_buf) + 1), *token = malloc(sizeof(setting_buf) + 1); - char *settingname; - int cnt = 0; - bool isCorrectSetting = true; - char equalchar[] = "="; - /* - Available settings: - "autoindent", - "autosyntax", - "codepage", - "convertnull", - "converttab", - "curinsert", - "cursize", - "devmode", - "fontcolor", - "linecount", - "linecountwide", - "macro", - "manfile", - "menucolor", - "mouse", - "newline", - "oldkeybindings", - "sigsegv", - "syntax", - "tabwide", - "trimlonglines", - "xsize", - "ysize", - */ - - // Set the non changing settings - SetColor(FG_DEFAULT); - SetColor(BG_DEFAULT); - default_color = DEFAULT_SYNTAX_COLOR; - strncpy_n(Tab_stack[file_index].newline, "\n", strlen_n(Tab_stack[file_index].newline)); - run_macro[0] = 0; - last_known_exception = NEWTRODIT_CRASH_INVALID_SETTINGS; - while (fgets(setting_buf, sizeof(setting_buf), settings)) + print_message(NEWTRODIT_PROMPT_QUIT); + if (yes_no_prompt()) { - setting_buf[strcspn(setting_buf, "\n")] = '\0'; // Remove newline - - cnt = strspn(setting_buf, " \t"); - // memmove(&setting_buf[0], &setting_buf[cnt], (strlen_n(setting_buf) - cnt)); - // memset(&setting_buf[strlen_n(setting_buf) - cnt], 0, (strlen_n(setting_buf) - cnt)); - // snprintf(setting_buf, sizeof(setting_buf), "%s", setting_buf - 1); + end_editor(); + exit(1); + } + ed.dirty = true; +} - if (setting_buf[cnt] == ';' || setting_buf[cnt] == 0) // Comment or newline found - { - continue; - } - iniptr = strtok(setting_buf, "="); - settingname = strdup(iniptr); - strlwr(settingname); +int handle_keystrokes(InputUTF8 ch, File *tstack) +{ + if (_ypos >= tstack->alloc_lines - 1) + change_allocated_lines(tstack, tstack->alloc_lines, tstack->alloc_lines + LINE_Y_INCREASE); - while (iniptr != NULL) // Loop through the settings + char *ptr = NULL, *ptr2 = NULL; + utf8_int32_t codep = 0; + if (ch.flags != 0) + { + switch (ch.flags) { - isCorrectSetting = true; - // iniptr[strcspn(iniptr, "\n")] = '\0'; - token = strtok(NULL, equalchar); - if (!strcmp(settingname, "fontcolor")) - { - bg_color = HexStrToDec(token) % 256; - default_color = bg_color; - } - else if (!strcmp(settingname, "autoindent")) - { - SetBoolValue(&autoIndent, token); - } - else if (!strcmp(settingname, "autosyntax")) - { - SetBoolValue(&autoLoadSyntaxRules, token); - } - else if (!strcmp(settingname, "codepage")) + case LEFT: + if (_uxpos > 0) { - int cp = atoi(token); -#ifdef _WIN32 - SetConsoleOutputCP(cp); -#else - // Linux TODO -#endif - } - else if (!strcmp(settingname, "convertnull")) - { - SetBoolValue(&convertNull, token); + previous_char_xpos(tstack->line[_ypos]->str, &_xpos); + _uwxpos -= previous_char_uwlen(tstack->line[_ypos]->str, _xpos); + + _uxpos--; } - else if (!strcmp(settingname, "converttab")) + else if (_ypos > 1) { - SetBoolValue(&convertTabtoSpaces, token); + _xpos = tstack->line[--_ypos]->len; + _uxpos = tstack->line[_ypos]->ulen; + _uwxpos = utf8wnlen(tstack->line[_ypos]->str, _uxpos); + decrease_scroll(tstack, 1); } - else if (!strcmp(settingname, "curinsert")) + break; + case RIGHT: + if (_uxpos < tstack->line[_ypos]->ulen) { - SetBoolValue(&cursorSizeInsert, token); - } - else + _uwxpos += next_char_uwlen(tstack->line[_ypos]->str, _xpos); + next_char_xpos(tstack->line[_ypos]->str, &_xpos); - if (!strcmp(settingname, "cursize")) - { - CURSIZE = atoi(token); - SetCursorSettings(true, CURSIZE); - } - else if (!strcmp(settingname, "devmode")) - { - SetBoolValue(&devMode, token); - } - else if (!strcmp(settingname, "findinsensitive")) - { - SetBoolValue(&findInsensitive, token); - } - else if (!strcmp(settingname, "linecount")) - { - SetBoolValue(&lineCount, token); + _uxpos++; } - else if (!strcmp(settingname, "linecountwide")) + else if (_ypos < tstack->linecount) { - LINECOUNT_WIDE = abs(atoi(token)) % sizeof(long long); + _xpos = 0; + _uxpos = 0; + _uwxpos = 0; + _ypos++; + increase_scroll(tstack, 1); } - else if (!strcmp(settingname, "linehighlight")) + break; + case DOWN: + if (_ypos <= tstack->linecount) { - SetBoolValue(&linecountHighlightLine, token); + _ypos++; + if (_uxpos > tstack->line[_ypos]->ulen) + _uxpos = tstack->line[_ypos]->ulen; + _xpos = utf8getxpos(tstack->line[_ypos]->str, _uxpos); + set_status_msg(false, "Down: %zu", utf8wnlen(tstack->line[_ypos]->str, _uxpos)); + _uwxpos = utf8wnlen(tstack->line[_ypos]->str, _uxpos); + increase_scroll(tstack, 1); } - else if (!strcmp(settingname, "macro")) - { - if (ValidString(token)) - { - strncpy_n(run_macro, token, MACRO_ALLOC_SIZE); - } - } - else if (!strcmp(settingname, "manfile")) + break; + case UP: + if (_ypos > 1) { - RemoveQuotes(token); // Remove quotes + _ypos--; - if (ValidFileName(token)) + if (_uxpos > tstack->line[_ypos]->ulen) { - strncpy_n(manual_file, token, sizeof(manual_file)); - manual_file[strcspn(manual_file, "\n")] = 0; + _uxpos = tstack->line[_ypos]->ulen; + _xpos = utf8getxpos(tstack->line[_ypos]->str, _uxpos); + _uwxpos = utf8wnlen(tstack->line[_ypos]->str, _uxpos); } - else - { - WriteLogFile("%s%s", NEWTRODIT_FS_FILE_INVALID_NAME, token); - } - } - else if (!strcmp(settingname, "menucolor")) - { - fg_color = (HexStrToDec(token) * 16) % 256; } - else if (!strcmp(settingname, "mouse")) + break; + case DEL: + if (_xpos < tstack->line[_ypos]->len) { - SetBoolValue(&partialMouseSupport, token); + ptr = tstack->line[_ypos]->str + _xpos; + ptr2 = utf8codepoint(tstack->line[_ypos]->str + _xpos, &codep); + if (delete_str(tstack->line[_ypos], _xpos, ptr2 - ptr)) + tstack->line[_ypos]->ulen--; } else - - if (!strcmp(settingname, "newline")) { - - if (!strncmp(token, "0x", 2)) - { - ParseHexString(token); - } - else + /* if (_ypos > 1) { - strncpy_n(Tab_stack[file_index].newline, token, strlen_n(Tab_stack[file_index].newline)); - } + _ypos--; + tstack->uxpos = tstack->line[_ypos]->ulen; + tstack->xpos = tstack->line[_ypos]->len; + tstack->linecount--; + } */ } - else + break; - if (!strcmp(settingname, "oldkeybindings")) - { - SetBoolValue(&useOldKeybindings, token); - } - else if (!strcmp(settingname, "sigsegv")) + case CTRLF4: + case ALTF4: + quit_newtrodit(tstack); + break; + case 0xc0000043: + if (ch.flags & ALT_BITMASK) { - SetBoolValue(sigsegv, token); + printf("Debug initiated crash\n"); + raise(SIGSEGV); } - else if (!strcmp(settingname, "syntax")) + break; + } + } + if (ch.utf8char == CTRLI && devMode) + { + if (ch.flags & SHIFT_BITMASK) + { + char inbuf[100]; + clear_partial(0, ed.ysize - 2, ed.xsize, 1); + printf("Insert hex: "); + scanf("%s", inbuf); + //(inbuf, sizeof inbuf, stdin); + // inbuf[strcspn(inbuf, "\r\n")] = '\0'; + display_status(tstack, NULL); + display_cursor_pos(tstack, NULL); + ch.utf8char = strtol(inbuf, NULL, 16); + ch.flags = 0; + } + } + + if (ch.utf8char != 0) + { + switch (ch.utf8char) + { + case BS: + if (_xpos > 0) { - RemoveQuotes(token); // Remove quotes - if (ValidFileName(token)) + ptr = tstack->line[_ypos]->str + _xpos; + ptr2 = utf8rcodepoint(tstack->line[_ypos]->str + _xpos, &codep); + size_t temp_xpos = previous_char_uwlen(tstack->line[_ypos]->str, _xpos - 1); + + if (delete_str(tstack->line[_ypos], _xpos - (ptr - ptr2), ptr - ptr2)) { + // printf("delstr ok %zu: %zu", temp_xpos, _xpos); - if (!strcmp(token, "1") || !strcmp(token, "true")) // Enable syntax highlighting but don't load any file - { - syntaxHighlighting = true; - } - else if (!strcmp(token, "0") || !strcmp(token, "false")) - { - syntaxHighlighting = false; - } - else - { - strncpy_n(syntax_filename, token, sizeof(syntax_filename)); - syntax_filename[strcspn(syntax_filename, "\n")] = 0; - if ((syntaxKeywordsSize = LoadSyntaxScheme(syntax_filename, &Tab_stack[file_index]))) - { - Tab_stack[file_index].Syntaxinfo.keyword_count = syntaxKeywordsSize; - } + _xpos -= ptr - ptr2; + _uwxpos -= temp_xpos; - syntaxHighlighting = true; - } + _uxpos--; + tstack->line[_ypos]->ulen--; } } - else if (!strcmp(settingname, "tabwide")) - { - TAB_WIDE = atoi(token); - } - else if (!strcmp(settingname, "trimlonglines")) - { - SetBoolValue(&trimLongLines, token); - } - else if (!strcmp(settingname, "wholeword")) - { - SetBoolValue(&matchWholeWord, token); - } - else if (!strcmp(settingname, "xsize")) - { - int xs = atoi(token); - SetConsoleSize(xs, YSIZE, xs, YSIZE); - } - else if (!strcmp(settingname, "ysize")) - { - int ys = atoi(token); - SetConsoleSize(XSIZE, ys, XSIZE, ys); - } else { - isCorrectSetting = false; - } - if (isCorrectSetting) - { - WriteLogFile("Loaded setting: %s (value '%s')", settingname, token); - } + if (_ypos > 1) + { + _ypos--; + tstack->uxpos = tstack->line[_ypos]->ulen; + tstack->xpos = tstack->line[_ypos]->len; + tstack->uwxpos = utf8wlen(tstack->line[_ypos]->str); + tstack->linecount--; + } + } + break; + case ENTER: + split_row(tstack, _xpos, &_ypos); + increase_scroll(tstack, 1); + + break; + case CTRLS: + save_file(tstack, tstack->filename, ch.flags & SHIFT_BITMASK); // If Shift is pressed, always display save as dialog + break; + case CTRLW: + close_file(tstack); + break; + case CTRLO: + open_file(tstack); + break; + case CTRLQ: + quit_newtrodit(tstack); + break; + case CTRLC: + break; + case CTRLD: + if (ch.flags & SHIFT_BITMASK) + toggle_option(&devMode, NEWTRODIT_DEV_TOOLS); else + set_status_msg(true, "%s", tstack->line[_ypos]->str); + + break; + case ESC: + clear_status_msg(); + display_status(tstack, NULL); + break; + default: + + if (ch.utf8char > 31) { - WriteLogFile("Unexistent setting: %s", settingname); + if (tstack->linecount == 0) + tstack->linecount++; + insert_utf8_char(tstack->line[_ypos], ch.utf8char, _xpos); + _xpos += codepoint_len(ch.utf8char); + _uxpos++; + _uwxpos += codepoint_width(NULL, ch.utf8char); + tstack->file_flags |= IS_MODIFIED; } - iniptr = strtok(NULL, "="); + break; } } - WriteLogFile("Finished loading settings file"); - - fclose(settings); - chdir(SInf.dir); + if ((ch.utf8char > 31 || (ch.utf8char == 8 && !(ch.flags & SHIFT_BITMASK))) && !syntaxHighlighting) + render_line(tstack, _ypos); - return 1; + return 0; } -int main(int argc, char *argv[]) +int editor_main() { - // Newtrodit initialization begins - - // Generate log file name - SInf.log_file_name = GetLogFileName(); - SInf.using_log = !!WriteLogFile("\nNewtrodit started"); - - WriteLogFile("Logfile opened with name: %s", SInf.log_file_name); - - if (!_isatty(_fileno(stdout)) || !_isatty(_fileno(stderr))) - { - /* - If for some reason stdout or stderr are still redirected to a file, show an error and quit - */ - fprintf(stderr, "%s", NEWTRODIT_ERROR_REDIRECTED_TTY); - return 1; - } - - // Redirect stdout to the console - - char *startup_info = calloc(sizeof(char), MAX_PATH * 2); // *2 for safety -#ifdef _WIN32 - GetModuleFileNameA(NULL, startup_info, MAX_PATH); -#else - startup_info = argv[0]; // Program name -#endif - -#ifdef _WIN32 - HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE), hStdin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hStdout, &dwStdoutMode); - GetConsoleMode(hStdin, &dwStdinMode); - // Disable wrapping to avoid (or at least reduce) graphical bugs - WriteLogFile("Changing console output mode: %s", (SetConsoleMode(hStdout, dwStdoutMode & ~ENABLE_WRAP_AT_EOL_OUTPUT)) ? "Succeeded" : "Failed"); - WriteLogFile("Changing console input mode: %s", (SetConsoleMode(hStdin, ENABLE_MOUSE_INPUT | 0x80 | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) ? "Succeeded" : "Failed"); // 0x40 is quick edit mode and 0x80 are extended flags - WriteLogFile("Loading startup data"); -#endif - - char *sinf_ptr = calloc(sizeof(char), MAX_PATH * 2); - if (get_path_directory(startup_info, sinf_ptr) != NULL) - { - SInf.dir = strdup(getcwd(NULL, 0)); - SInf.location = strdup(sinf_ptr); - } - else - { - memset(SInf.location, 0, sizeof(char) * MAX_PATH * 2); - } - free(sinf_ptr); - - if (BUFFER_X < MIN_BUFSIZE || BUFFER_Y < MIN_BUFSIZE) // Check if buffer size is too small - { - char *err_msg = malloc(sizeof(char) * MIN_BUFSIZE); - - snprintf(err_msg, MIN_BUFSIZE, "Buffer is too small (Current size is %dx%d and minimum size is %dx%d).", BUFFER_X, BUFFER_Y, MIN_BUFSIZE, MIN_BUFSIZE); -#ifdef _WIN32 - MessageBox(NULL, err_msg, "Newtrodit", MB_ICONERROR); -#endif - WriteLogFile(err_msg); - fprintf(stderr, "%s", err_msg); - free(err_msg); - ExitRoutine(1); - } - - SInf.argv = argv; // Save only its memory address, not the actual value - SInf.argc = argc; - SInf.xsize = XSIZE; - SInf.ysize = YSIZE; - SInf.xbuf = GetConsoleInfo(XBUFFER_SIZE); - SInf.ybuf = GetConsoleInfo(YBUFFER_SIZE); -#ifdef _WIN32 - SetConsoleSize(SInf.xsize, SInf.ysize, SInf.xsize, SInf.ysize); // Remove all the borders -#endif - SInf.manual_open = 0; // Times manual has been open - SInf.save_buffer = false; - - clearAllBuffer = true; - - int replaceChar = false; // Replace or insert characters - int sigsegvScreen = true; - int listDir = true; - - for (int i = 0; i < MAX_TABS; i++) - { - Tab_stack[i].filename = calloc(MAX_PATH, sizeof(char)); - } - - // Allocate buffer - - if (!AllocateBufferMemory(&Tab_stack[file_index])) - { - - printf("%.*s\n", wrapSize, NEWTRODIT_ERROR_OUT_OF_MEMORY); - ExitRoutine(ENOMEM); - } - - old_open_files = malloc(MIN_BUFSIZE * sizeof(char *)); - - for (int i = 1; i < MIN_BUFSIZE; i++) - { - old_open_files[i] = calloc(MAX_PATH, sizeof(char)); - } - - run_macro = calloc(sizeof(char), MACRO_ALLOC_SIZE + 1); - SetWrapSize(); - WriteLogFile("Setting console signal handlers"); -#ifdef _WIN32 - signal(SIGBREAK, SIG_IGN); // Ctrl-Break handler -#else - signal(SIGTSTP, SIG_IGN); // Ctrl-Z handler -#endif - signal(SIGINT, SIG_IGN); // Ctrl-C handler - signal(SIGSEGV, sigsegv_handler); // Segmentation fault handler - signal(SIGABRT, sigabrt_handler); // Abort handler - signal(SIGTRAP, sigtrap_handler); - signal(SIGFPE, sigfpe_handler); - WriteLogFile("Finished setting console signal handlers"); - - LoadSettings(settings_file, run_macro, &sigsegvScreen, &Tab_stack[file_index]); // Load settings from settings file - - char *temp_strsave = calloc(sizeof(char), BUFFER_X + 1); - char *tmp = calloc(sizeof(char), BUFFER_X + 1); - char *syntax_file = calloc(MAX_PATH + 1, sizeof(char)), *find_string = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)), *replace_string = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); - // int undo_stack_tree = 0; - // Declare variables - int old_x_size = 0, old_y_size = 0; - int bs_tk = 0; - - SInf.color = GetConsoleInfo(COLOR); - int replace_count = 0; - char *replace_str_ptr; - - char inbound_ctrl_key[100] = {0}; - char *newname = calloc(MAX_PATH + 1, sizeof(char)), *locate_file = calloc(MAX_PATH + 1, sizeof(char)), *macro_input = calloc(MACRO_ALLOC_SIZE + 1, sizeof(char)), *fcomp1 = calloc(MAX_PATH + 1, sizeof(char)), *fcomp2 = calloc(MAX_PATH + 1, sizeof(char)), *command_palette = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(char)); - convertTabtoSpaces = true; - int n = 0, n2 = 0; - char *ptr = calloc(sizeof(char), BUFFER_X), *buffer_clipboard; - - SetCurrentDirectory(SInf.dir); - - /* - int *relative_xpos = calloc(sizeof(int) * Tab_stack[file_index].bufy, BUFFER_X); - int *relative_ypos = calloc(sizeof(int) * BUFFER_X, Tab_stack[file_index].bufy); - */ - - int ch = 0; // Character variable - - int *file_arguments = {0}; // Array of ints for arguments that aren't switches - int file_arguments_count = 0; - - file_arguments = calloc(sizeof(int *), argc); // Allocate memory for file_arguments for each argument - WriteLogFile("Finished loading startup data"); - - WriteLogFile("Parsing command-line arguments"); - ParseArguments(argc, argv, file_arguments, &file_arguments_count); - - if (file_arguments_count > 0) - { - open_files = file_arguments_count; - for (int i = 1; i <= file_arguments_count; i++) - { - Tab_stack[file_index].filename = argv[file_arguments[i]]; - if ((n = LoadFile(&Tab_stack[file_index], Tab_stack[file_index].filename)) <= -1) - { - fprintf(stderr, "%s", ErrorMessage(-n, Tab_stack[file_index].filename)); - return errno; - } + InputUTF8 ch; + Line *lptr; - if (file_arguments_count > 1 && i < file_arguments_count) // file_arguments_count - 1 iterations to not allocate an extra buffer - { - if (file_index < MAX_TABS) - { - file_index++; - if (!AllocateBufferMemory(&Tab_stack[file_index])) // Allocate more memory for next file - { - printf("%.*s\n", wrapSize, NEWTRODIT_ERROR_OUT_OF_MEMORY); - ExitRoutine(ENOMEM); - } - } - } - } - } - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - LoadAllNewtrodit(); - clearAllBuffer = false; - - if (partialMouseSupport) - { - WriteLogFile("Mouse enabled."); - } while (1) { - UpdateTitle(&Tab_stack[file_index]); - SetDisplayY(&Tab_stack[file_index]); - - old_y_size = YSIZE; - old_x_size = XSIZE; - - if ((lineCount && !isprint(ch) && c != -32) || linecountHighlightLine) // -32 = scroll - { - DisplayLineCount(&Tab_stack[file_index], YSIZE - 3, Tab_stack[file_index].display_y); - } - if (c == -32) - { - c = 0; - } - - if (c != -2) // Clear bottom line - { - DisplayCursorPos(&Tab_stack[file_index]); - } - SetWrapSize(); - SetDisplayX(&Tab_stack[file_index]); - - SetDisplayCursorPos(&Tab_stack[file_index]); -#ifdef _WIN32 - CompareFileWriteTime(&Tab_stack[file_index]); -#endif - - if (Tab_stack[file_index].selection.is_selected) - { - WriteLogFile("Selection: (%d,%d) to (%d,%d)", Tab_stack[file_index].selection.start.x, Tab_stack[file_index].selection.start.y, Tab_stack[file_index].selection.end.x, Tab_stack[file_index].selection.end.y); - SelectPrint(&Tab_stack[file_index], _ypos); - } - if (devMode && (Tab_stack[file_index].strsave[_ypos][_xpos] > 127 || Tab_stack[file_index].strsave[_ypos][_xpos] < 0)) - { - PrintBottomString("Dev mode warning: Possible stack overrun found: '%c' (0x%02x) at X=%d Y=%d", Tab_stack[file_index].strsave[_ypos][_xpos], Tab_stack[file_index].strsave[_ypos][_xpos], _xpos, _ypos); - c = -2; - } - ch = GetNewtroditInput(&Tab_stack[file_index]); // Register all input events, not only key presses + set_display_pos(file[ed.file_index]); - if ((_xpos < 0 || _ypos < 1) || _xpos > strlen_n(Tab_stack[file_index].strsave[_ypos]) || _ypos > Tab_stack[file_index].linecount + 1 || _ypos >= Tab_stack[file_index].bufy) - { - PrintBottomString("%s", NEWTRODIT_ERROR_INVALID_POS_RESET); - WriteLogFile("(%d,%d) %s", _xpos, _ypos, NEWTRODIT_ERROR_INVALID_POS_RESET); - getch_n(); - _xpos = 0; - _ypos = 1; - c = -2; - } + display_line_numbering(file[ed.file_index], _ypos); + gotoxy(_uwxpos + file[ed.file_index]->linenumber_wide + file[ed.file_index]->linenumber_padding, file[ed.file_index]->scroll_pos.y); - if (c == -2) // Inbound invalid control key - { - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); + ch = get_newtrodit_input(); - gotoxy(_xpos + (lineCount ? Tab_stack[file_index].linecount_wide : 0), _ypos); - c = 0; - } - if (!allowAutomaticResizing) + handle_keystrokes(ch, file[ed.file_index]); + if (ed.status_msg == NULL && ed.dirty) { - if (old_x_size != XSIZE || old_y_size != YSIZE) // Check if size has been modified - { - n = 0; - while (!ValidSize()) // At 3 message boxes, close the program - { - if (n == 2) - { - ClearScreen(); - fprintf(stderr, "%s\n", NEWTRODIT_ERROR_WINDOW_TOO_SMALL); - ExitRoutine(1); - } - n++; - } - clearAllBuffer = true; - - LoadAllNewtrodit(); - clearAllBuffer = false; - - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } + display_bottom_bar(NULL); + ed.dirty = false; } - if (ch == CTRLA) // S-^A = Toggle auto syntax highlighting loading + if (ed.displayStatusOnce && !ed.dirty) { - if (CheckKey(VK_SHIFT)) - { - ToggleOption(&autoLoadSyntaxRules, NEWTRODIT_AUTO_SYNTAX_LOAD, false); - c = -2; - } - else - { - SelectStart(&Tab_stack[file_index], 0, 1); - SelectEnd(&Tab_stack[file_index], strlen_n(Tab_stack[file_index].strsave[Tab_stack[file_index].linecount]), Tab_stack[file_index].linecount); - } - ch = 0; - continue; + clear_status_msg(); + display_status(file[ed.file_index], ed.status_msg); + ed.displayStatusOnce = false; } - if (ch == CTRLC || ch == CTRLK) // ^C = Copy line to clipboard; ^K = Cut line + if (_xpos >= file[ed.file_index]->line[_ypos]->bufx - 1) { - if (!CheckKey(VK_SHIFT)) - { - if (Tab_stack[file_index].strsave[_ypos][0] != '\0') - { - -#ifdef _WIN32 - SetClipboardNewtrodit(Tab_stack[file_index].strsave[_ypos]); - - if (ch == CTRLK && useOldKeybindings) - { - if (Tab_stack[file_index].strsave[_ypos][0] != '\0') - { - memset(Tab_stack[file_index].strsave[_ypos], 0, strlen_n(Tab_stack[file_index].strsave[_ypos])); - strncpy_n(Tab_stack[file_index].strsave[_ypos], Tab_stack[file_index].newline, strlen_n(Tab_stack[file_index].newline)); - ClearPartial((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y, XSIZE - (lineCount ? Tab_stack[file_index].linecount_wide : 0), 1); - _xpos = 0; - } - } -#endif - } - ch = 0; - continue; - } - else + lptr = increase_line(file[ed.file_index]->line[_ypos], LINE_SIZE); + if (!lptr) { - if (ch == CTRLK) - { - ToggleOption(&useOldKeybindings, NEWTRODIT_OLD_KEYBINDINGS, true); - - c = -2; - ch = 0; - continue; - } - if (ch == CTRLC) // S-^C = File compare - { - ClearPartial(0, YSIZE - 2, XSIZE, 2); - printf("%.*s\n", wrapSize, NEWTRODIT_PROMPT_FIRST_FILE_COMPARE); - printf("%.*s", wrapSize, NEWTRODIT_PROMPT_SECOND_FILE_COMPARE); - gotoxy(strlen_n(NEWTRODIT_PROMPT_FIRST_FILE_COMPARE), YSIZE - 2); - fcomp1 = TypingFunction(32, 255, MAX_PATH, NULL); - if (fcomp1[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], fcomp1); - continue; - } - gotoxy(strlen_n(NEWTRODIT_PROMPT_SECOND_FILE_COMPARE), BOTTOM); - - fcomp2 = TypingFunction(32, 255, MAX_PATH, NULL); - if (fcomp2[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], fcomp2); - continue; - } - - RemoveQuotes(fcomp1); - RemoveQuotes(fcomp2); - - if (!strcmp(fcomp1, fcomp2)) - { - PrintBottomString("%s", NEWTRODIT_FS_SAME_FILE); - getch_n(); - } - else - { - FileCompare(fcomp1, fcomp2); - } - - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - free(fcomp1); - free(fcomp2); - ch = 0; - continue; - } + print_message(NEWTRODIT_ERROR_ALLOCATION_FAILED); + getch(); + ed.dirty = true; } } + correct_position(file[ed.file_index]->line[_ypos], &_xpos, &_uxpos, &_uwxpos); - if (ch == CTRLG) // ^G = Go to line; S-^G = Go to column + if (ed.dirty) { - GotoBufferPosition(&Tab_stack[file_index], 0, CheckKey(VK_SHIFT)); - ch = 0; - continue; + display_status(file[ed.file_index], ed.status_msg); + if (ed.displayStatusOnce) + ed.dirty = false; } - - if (ch == CTRLI && !CheckKey(VK_TAB)) // ^I = File info + else { - if (!CheckKey(VK_SHIFT)) - { - CountBufferLines(&Tab_stack[file_index]); - PrintBottomString("File: \'%s\', size: %lld bytes (%u lines). File type: %s. Syntax highlighting: %s", StrLastTok(Tab_stack[file_index].filename, PATHTOKENS), Tab_stack[file_index].size, Tab_stack[file_index].linecount, Tab_stack[file_index].language, Tab_stack[file_index].Syntaxinfo.syntax_lang); - c = -2; - ch = 0; - } + display_cursor_pos(file[ed.file_index], NULL); } + set_scroll(file[ed.file_index]); + display_line(file[ed.file_index], _ypos, file[ed.file_index]->line[_ypos]->rlen); + } + return 1; +} - if (ch == CTRLL) // ^L = Toggle line count; S-^L = Locate files - { - if (!CheckKey(VK_SHIFT)) - { - ToggleOption(&lineCount, NEWTRODIT_LINE_COUNT, true); - c = -2; - } - else - { - // List all files in the directory +int main(int argc, char* argv[]) +{ - PrintBottomString("%s", NEWTRODIT_PROMPT_LOCATE_FILE); - locate_file = TypingFunction(32, 255, MAX_PATH, NULL); - if (locate_file[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], locate_file); - continue; - } + setlocale(LC_ALL, ".utf8"); + init_editor(argc, argv); - ClearPartial(0, 1, XSIZE, YSIZE - 1); - if (!LocateFiles(listDir, locate_file, 0)) - { - getch_n(); - } - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - ClearPartial(0, 1, XSIZE, YSIZE - 2); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - free(locate_file); - } - continue; - } + editor_main(); - if (ch == CTRLN) // ^N = New file - { - if (!CheckKey(VK_SHIFT)) - { - - if ((n = NewFile(&Tab_stack[file_index])) > 0) - { - PrintBottomString("%s", NEWTRODIT_NEW_FILE_CREATED); - } - else - { - ErrorMessage(n, Tab_stack[file_index].filename); - } - } - else - { - - ToggleOption(&convertNull, NEWTRODIT_NULL_CONVERSION, false); - } - c = -2; - - ch = 0; - continue; - } - if (ch == CTRLO) // ^O = Open file - { - if (!CheckKey(VK_SHIFT)) - { - OpenNewtroditFile(&Tab_stack[file_index], NULL); - } - ch = 0; - } - if (ch == CTRLP) // ^P = Command palette - { - ch = 0; - - PrintBottomString("> "); - command_palette = TypingFunction(32, 255, DEFAULT_ALLOC_SIZE, NULL); - if (command_palette[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], command_palette); - continue; - } - if (!ParseCommandPalette(&Tab_stack[file_index], command_palette)) - { - PrintBottomString(NEWTRODIT_ERROR_UNKNOWN_COMMAND, command_palette); - c = -2; - } - free(command_palette); - } - - if (ch == CTRLT) // S-^T = Toggle tab conversion - { - /* if (CheckKey(VK_SHIFT) && devMode) - { - ToggleOption(&convertTabtoSpaces, NEWTRODIT_TAB_CONVERSION, false); - c = -2; - - ch = 0; - continue; - } */ - if (CheckKey(VK_SHIFT)) // S-^T - { - if (old_open_files[0][0] != '\0' && oldFilesIndex > 0) - { - if (Tab_stack[file_index].is_modified) - { - PrintBottomString("%s", NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE); - - if (YesNoPrompt()) - { - if (SaveFile(&Tab_stack[file_index], NULL, false)) - { - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - continue; - } - } - } - PrintBottomString("%s", NEWTRODIT_PROMPT_REOPEN_FILE); - if (YesNoPrompt()) - { - if (LoadFile(&Tab_stack[file_index], old_open_files[oldFilesIndex])) - { - strncpy_n(old_open_files[oldFilesIndex], Tab_stack[file_index].filename, MAX_PATH); - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - } - } - ch = 0; - } - continue; - } - - if (ch == CTRLR) // ^R = Reload file and S-^R = Reload settings - { - - if (CheckKey(VK_SHIFT)) // S-^R = Reload settings - { - - PrintBottomString("%s", NEWTRODIT_PROMPT_RELOAD_SETTINGS); - if (YesNoPrompt()) - { - if (LoadSettings(settings_file, run_macro, &sigsegvScreen, &Tab_stack[file_index])) // Reload settings - { - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - PrintBottomString("%s", NEWTRODIT_SETTINGS_RELOADED); - } - else - { - PrintBottomString("%s", NEWTRODIT_ERROR_RELOAD_SETTINGS); - } - getch_n(); - } - DisplayCursorPos(&Tab_stack[file_index]); - ShowBottomMenu(); - } - else - { - - // ^R = Reload file - PrintBottomString("%s", NEWTRODIT_PROMPT_RELOAD_FILE); - - if (YesNoPrompt()) - { - ReloadFile(&Tab_stack[file_index]); - if (Tab_stack[file_index].is_readonly && !Tab_stack[file_index].is_untitled) - { - PrintBottomString("%s", NEWTRODIT_WARNING_READONLY_FILE); - c = -2; - } - } - else - { - ShowBottomMenu(); - } - } - ch = 0; - - continue; - } - - if (ch & BIT_ESC224) // Special keys: 224 (0xE0) - { - - c = -32; - -#ifdef _WIN32 - switch (ch & (~BIT_ESC224)) -#else - switch (ch) -#endif - { - - case UP: - // Up arrow - if (_ypos > 1) - { - SelectCheck(&Tab_stack[file_index]); - - _ypos--; - - if (Tab_stack[file_index].strsave[_ypos][_xpos] == '\0') - { - - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - SetDisplayX(&Tab_stack[file_index]); - } - - SelectEnd(&Tab_stack[file_index], _xpos, _ypos); - } - if (!UpdateHorizontalScroll(&Tab_stack[file_index], false)) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - break; - - case LEFT: - // Left arrow - - if (_xpos > 0) - { - SelectCheck(&Tab_stack[file_index]); - - if (Tab_stack[file_index].strsave[_ypos][_xpos - 1] == 9) - { - // relative_xpos[_ypos] -= TAB_WIDE; - } - - _xpos--; - - SetDisplayX(&Tab_stack[file_index]); - UpdateHorizontalScroll(&Tab_stack[file_index], false); - } - else if (_ypos > 1) - { - SelectCheck(&Tab_stack[file_index]); - - _xpos = NoLfLen(Tab_stack[file_index].strsave[--_ypos]); - SetDisplayX(&Tab_stack[file_index]); - UpdateScrolledScreen(&Tab_stack[file_index]); - } - - break; - case RIGHT: - // Right arrow - - if (Tab_stack[file_index].strsave[_ypos][_xpos] != '\0') - { - if (_xpos == NoLfLen(Tab_stack[file_index].strsave[_ypos])) - { - if (_ypos < Tab_stack[file_index].bufy - 1) - { - if (Tab_stack[file_index].strsave[_ypos + 1][0] != '\0' || LineContainsNewLine(&Tab_stack[file_index], _ypos)) - { - SelectCheck(&Tab_stack[file_index]); - - _xpos = 0; - _ypos++; - if (!UpdateHorizontalScroll(&Tab_stack[file_index], true)) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - } - } - } - else - { - SelectCheck(&Tab_stack[file_index]); - - _xpos++; - SelectEnd(&Tab_stack[file_index], _xpos, _ypos); - - SetDisplayX(&Tab_stack[file_index]); - UpdateHorizontalScroll(&Tab_stack[file_index], false); - } - } - if (BufferLimit(&Tab_stack[file_index])) - { - ShowBottomMenu(); - continue; - } - - break; - case DOWN: - // Down arrow - - if (_ypos <= Tab_stack[file_index].linecount) - { - n2 = _ypos; - if (_ypos < Tab_stack[file_index].bufy - 1) - { - if (Tab_stack[file_index].strsave[_ypos + 1][0] != '\0' || LineContainsNewLine(&Tab_stack[file_index], _ypos)) - { - SelectCheck(&Tab_stack[file_index]); - - if (Tab_stack[file_index].strsave[_ypos + 1][_xpos] == '\0') - { - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos + 1]); // Add tab wide - } - _ypos++; - } - } - if (BufferLimit(&Tab_stack[file_index])) - { - _ypos = n2; // Restore ypos if a position outside the buffer is reached - ShowBottomMenu(); - continue; - } - if (!UpdateHorizontalScroll(&Tab_stack[file_index], true)) - { - if (_ypos <= Tab_stack[file_index].linecount) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - } - } - - break; - - case PGUP: - - n = _xpos; - - (_ypos < YSCROLL - 1) ? (_ypos = 1) : (_ypos -= YSCROLL - 1); - if (NoLfLen(Tab_stack[file_index].strsave[_ypos]) < n) - { - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - } - if (!UpdateHorizontalScroll(&Tab_stack[file_index], true)) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - break; - case PGDW: - if (_ypos < Tab_stack[file_index].linecount) - { - n = _xpos; - - (_ypos + YSCROLL - 1 > Tab_stack[file_index].linecount) ? (_ypos = Tab_stack[file_index].linecount) : (_ypos += YSCROLL - 1); - if (_ypos >= Tab_stack[file_index].bufy) - { - _ypos = Tab_stack[file_index].bufy - 1; - } - if (NoLfLen(Tab_stack[file_index].strsave[_ypos]) < n) - { - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - } - if (!UpdateHorizontalScroll(&Tab_stack[file_index], true)) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - } - break; - case HOME: - // HOME key - _xpos = 0; - SelectCheck(&Tab_stack[file_index]); - - UpdateHorizontalScroll(&Tab_stack[file_index], true); - - break; - case END: - // END key - SelectCheck(&Tab_stack[file_index]); - - n = _xpos; - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - if (n == _xpos) // Optimization - { - break; - } - SetDisplayX(&Tab_stack[file_index]); - UpdateHorizontalScroll(&Tab_stack[file_index], false); - break; - case CTRLEND: - if (_ypos < Tab_stack[file_index].linecount || (_ypos == Tab_stack[file_index].linecount && _xpos < NoLfLen(Tab_stack[file_index].strsave[_ypos]))) - { - SelectCheck(&Tab_stack[file_index]); - - // ^END key - _ypos = (Tab_stack[file_index].linecount >= Tab_stack[file_index].bufy) ? Tab_stack[file_index].bufy - 1 : Tab_stack[file_index].linecount; - Tab_stack[file_index].last_y = _ypos; - _xpos = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - SetDisplayX(&Tab_stack[file_index]); - - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - UpdateHorizontalScroll(&Tab_stack[file_index], false); - } - } - - break; - case CTRLHOME: - // ^HOME key - SelectCheck(&Tab_stack[file_index]); - - _xpos = 0; - _ypos = 1; - - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - UpdateHorizontalScroll(&Tab_stack[file_index], true); - } - - Tab_stack[file_index].last_y = _ypos; - - break; - - case INS: - // INS key - replaceChar = !replaceChar; - if (cursorSizeInsert) - { - replaceChar ? SetCursorSettings(true, CURSIZE_INS) : SetCursorSettings(true, CURSIZE); - } - - break; - case F12: // F12 - { - if (devMode) - { - PrintBottomString("Keyword count: %d. Language: %s", Tab_stack[file_index].Syntaxinfo.keyword_count, Tab_stack[file_index].Syntaxinfo.syntax_lang); - c = -2; - } - break; - } - case DEL: // DEL and S-DEL - // SelectDelete(&Tab_stack[file_index], true); - if (CheckKey(VK_SHIFT)) // S-DEL - { - if (Tab_stack[file_index].selection.is_selected) - { - SelectDelete(&Tab_stack[file_index], true); - } - else if (_ypos > 0) - { - DeleteRow(Tab_stack[file_index].strsave, _ypos, Tab_stack[file_index].bufy - 1); - - SetDisplayX(&Tab_stack[file_index]); - ClearPartial(lineCount ? (Tab_stack[file_index].linecount_wide) : 0, (Tab_stack[file_index].display_y + 1 > YSCROLL ? Tab_stack[file_index].display_y : YSCROLL), XSIZE - (lineCount ? (Tab_stack[file_index].linecount_wide) : 0), (YSIZE - Tab_stack[file_index].display_y) - 1); - DisplayFileContent(&Tab_stack[file_index], stdout, Tab_stack[file_index].display_y - 1); - - _xpos = 0; - Tab_stack[file_index].is_modified = true; - Tab_stack[file_index].linecount--; - } - } - else // DEL key - { - - if (Tab_stack[file_index].selection.is_selected) - { - SelectDelete(&Tab_stack[file_index], true); - } - else if (strlen_n(Tab_stack[file_index].strsave[_ypos]) > 0) // TODO: Fix this bug in the line number 1 a - { - SetDisplayY(&Tab_stack[file_index]); - DeleteChar(Tab_stack[file_index].strsave[_ypos], _xpos); - n = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - if (_xpos < n || (Tab_stack[file_index].strsave[_ypos][_xpos] == '\0' && _ypos >= Tab_stack[file_index].linecount)) // Is this needed? (2023/06/07) - { - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - ClearPartial(n + (lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y, 1, 1); - Tab_stack[file_index].is_modified = true; - } - else - { - if (_ypos < Tab_stack[file_index].bufy - 1 && _ypos < Tab_stack[file_index].linecount) // Don't try to delete a non-existing row - { - - memset(Tab_stack[file_index].strsave[_ypos] + n, 0, Tab_stack[file_index].bufx - n); // Empty the new line - strncat(Tab_stack[file_index].strsave[_ypos], Tab_stack[file_index].strsave[_ypos + 1], strlen_n(Tab_stack[file_index].strsave[_ypos + 1])); // Concatenate the next line - - DeleteRow(Tab_stack[file_index].strsave, _ypos + 1, Tab_stack[file_index].bufy - 1); // Delete the old row, shifting other rows up - Tab_stack[file_index].linecount--; - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - ClearPartial(0, Tab_stack[file_index].display_y, XSIZE, (YSIZE - Tab_stack[file_index].display_y) - 1); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - Tab_stack[file_index].is_modified = true; - } - } - } - } - break; - } - continue; - } -#ifdef _WIN32 - if (ch == CTRLM && !CheckKey(VK_RETURN) && !_NEWTRODIT_OLD_SUPPORT && !GetAsyncKeyState(VK_RETURN)) // ^M = Toggle mouse - { - if (!CheckKey(VK_SHIFT)) - { - - ToggleOption(&partialMouseSupport, NEWTRODIT_MOUSE, false); - c = -2; - ch = 0; - continue; - } - } - - if ((ch == ENTER && CheckKey(VK_RETURN)) || (ch == ENTER && _NEWTRODIT_OLD_SUPPORT)) // Enter character -#else - if (ch == ENTER) -#endif - { - SelectDelete(&Tab_stack[file_index], true); - if (_ypos < Tab_stack[file_index].bufy - 1 && Tab_stack[file_index].linecount < Tab_stack[file_index].bufy - 1) - { - - InsertNewRow(&Tab_stack[file_index], &_xpos, &_ypos, Tab_stack[file_index].display_y, Tab_stack[file_index].bufx, true, true); - _xpos = AutoIndent(&Tab_stack[file_index]); // Set the X position depending if auto indent is enabled or not - - if (!UpdateHorizontalScroll(&Tab_stack[file_index], true)) - { - UpdateScrolledScreen(&Tab_stack[file_index]); - } - - Tab_stack[file_index].linecount++; // Increment line count - Tab_stack[file_index].is_modified = true; - } - } - if (ch == CTRLE) // ^E = Toggle syntax highlighting / S-^E = Set syntax highlighting rules file - { - if (CheckKey(VK_SHIFT)) - { - PrintBottomString("%s", NEWTRODIT_PROMPT_SYNTAX_FILE); - - syntax_file = TypingFunction(32, 255, MAX_PATH, NULL); - if (syntax_file[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], syntax_file); - continue; - } - if (LoadSyntaxScheme(syntax_file, &Tab_stack[file_index])) // Change keywords size - { - syntaxHighlighting = true; - Tab_stack[file_index].Syntaxinfo.syntax_file = FullPath(syntax_file); - Tab_stack[file_index].Syntaxinfo.keyword_count = syntaxKeywordsSize; - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - - PrintBottomString(NEWTRODIT_SYNTAX_HIGHLIGHTING_LOADED, Tab_stack[file_index].Syntaxinfo.syntax_lang); - } - else - { - PrintBottomString("%s", NEWTRODIT_SYNTAX_HIGHLIGHTING_FAILED); - } - getch_n(); - - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - ch = 0; - continue; - } - else - { - ToggleOption(&syntaxHighlighting, NEWTRODIT_SYNTAX_HIGHLIGHTING, false); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - c = -2; - } - - ch = 0; - continue; - } - if (ch == CTRLF) // ^F = Find string - { - - // Empty values - // memset(find_string, 0, sizeof find_string); - n2 = _ypos; - - c = 0; - - findInsensitive = false; - if (CheckKey(VK_SHIFT)) - { - findInsensitive = true; - PrintBottomString("%s", NEWTRODIT_PROMPT_FIND_STRING_INSENSITIVE); - } - else - { - PrintBottomString("%s", NEWTRODIT_PROMPT_FIND_STRING); - } - - find_string = TypingFunction(32, 255, MAX_PATH, NULL); - if (find_string[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], find_string); - continue; - } - - FindNewtroditString(&Tab_stack[file_index], find_string); - - ch = 0; - continue; - } -#ifdef _WIN32 - if (ch & BIT_ESC0) -#else - if (ch & BIT_ESC224) -#endif - { - switch (ch & ~(BIT_ESC0)) - { - case CTRLALTR: // ^A-R (ROT13) - - if (rot13(Tab_stack[file_index].strsave[_ypos])) - { - ClearPartial(0, Tab_stack[file_index].display_y, XSIZE, 1); - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - } - break; - case CTRLALTU: // A-^U (Uppercase) - for (int i = 0; i < strlen_n(Tab_stack[file_index].strsave[_ypos]); i++) - { - Tab_stack[file_index].strsave[_ypos][i] = toupper(Tab_stack[file_index].strsave[_ypos][i]); - } - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - break; - - case CTRLALTL: // A-^L (Lowercase) - for (int i = 0; i < strlen_n(Tab_stack[file_index].strsave[_ypos]); i++) - { - Tab_stack[file_index].strsave[_ypos][i] = tolower(Tab_stack[file_index].strsave[_ypos][i]); - } - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - break; - case CTRLALTN: // A-^N (New file and save) - - if (CheckKey(VK_MENU)) - { - - if (NewFile(&Tab_stack[file_index])) - { - PrintBottomString("%s", NEWTRODIT_NEW_FILE_CREATED); - getch_n(); - SaveFile(&Tab_stack[file_index], NULL, true); - } - else - { - PrintBottomString("%s", NEWTRODIT_ERROR_NEW_FILE); - getch_n(); - } - - DisplayCursorPos(&Tab_stack[file_index]); - - ShowBottomMenu(); - } - break; - - case F1: // F1 key - - NewtroditHelp(); - - LoadAllNewtrodit(); - - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - DisplayCursorPos(&Tab_stack[file_index]); - break; - case F2: // F2 key - if (Tab_stack[file_index].is_untitled || !Tab_stack[file_index].is_saved) - { - if (!SaveFile(&Tab_stack[file_index], NULL, false)) - { - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - ch = 0; - continue; - } - } - - PrintBottomString("%s", NEWTRODIT_PROMPT_RENAME_FILE); - newname = TypingFunction(32, 255, MAX_PATH, NULL); - if (newname[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], newname); - break; - } - - if (!CheckFile(newname)) - { - PrintBottomString("%s", NEWTRODIT_PROMPT_OVERWRITE); - if (YesNoPrompt() && remove(newname)) - { - PrintBottomString("%s%s", NEWTRODIT_FS_FILE_DELETE, newname); - WriteLogFile("%s%s", NEWTRODIT_FS_FILE_DELETE, newname); - getch_n(); - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - free(newname); - break; - } - } -#ifdef _WIN32 - if (MoveFile(Tab_stack[file_index].filename, newname)) -#else - if (rename(Tab_stack[file_index].filename, newname)) -#endif - { - PrintBottomString("%s%s", NEWTRODIT_FILE_RENAMED, newname); - WriteLogFile("%s%s", NEWTRODIT_FILE_RENAMED, newname); - strncpy_n(Tab_stack[file_index].filename, newname, MAX_PATH); - - UpdateTitle(&Tab_stack[file_index]); - CenterText(StrLastTok(Tab_stack[file_index].filename, PATHTOKENS), 0); - DisplayTabIndex(&Tab_stack[file_index]); - - DisplayFileType(); - } - else - { - PrintBottomString("%s", NEWTRODIT_FS_FILE_RENAME); - WriteLogFile("%s", NEWTRODIT_FS_FILE_RENAME); - } - - free(newname); - - getch_n(); - - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - ch = 0; - break; - case F5: // F5 key = Run macro - if (!run_macro) - { - if (!Tab_stack[file_index].is_untitled && Tab_stack[file_index].is_saved) - { -#ifdef _WIN32 - GetFullPathName(Tab_stack[file_index].filename, sizeof(Tab_stack[file_index].filename), tmp, NULL); -#else - tmp = strdup(FullPath(Tab_stack[file_index].filename)); - StartProcess(tmp); -#endif - StartProcess(tmp); - } - } - else - { - tmp = strdup(run_macro); - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_CURRENT_FILE, StrLastTok(Tab_stack[file_index].filename, PATHTOKENS), &n); - if (Tab_stack[file_index].is_untitled) - { - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_FULL_PATH, Tab_stack[file_index].filename, &n); - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_CURRENT_DIR, SInf.dir, &n); - } - else - { - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_FULL_PATH, FullPath(Tab_stack[file_index].filename), &n); - get_path_directory(FullPath(Tab_stack[file_index].filename), ptr); - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_CURRENT_DIR, ptr, &n); - tmp = ReplaceString(strdup(tmp), NEWTRODIT_MACRO_CURRENT_EXTENSION, StrLastTok(Tab_stack[file_index].filename, "."), &n); - } - WriteLogFile("Running macro: %s", tmp); - StartProcess(tmp); - } - - ch = 0; - break; - case F6: // F6 key = Insert date and time - - temp_strsave = GetTime(showMillisecondsInTime); - - if (_xpos + strlen_n(temp_strsave) < Tab_stack[file_index].bufx - 1) // TODO: Change this - { - - n = strlen_n(Tab_stack[file_index].strsave[_ypos]); - InsertStr(Tab_stack[file_index].strsave[_ypos], temp_strsave, _xpos, false, Tab_stack[file_index].bufx); - - _xpos += strlen_n(Tab_stack[file_index].strsave[_ypos]) - n; // Increase the X position by substracting, prevent a cursor move if the insertion is successful - - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - - if (_xpos <= XSIZE) - { - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - } - } - - break; - - case F9: // F9 = Compile - if (!Tab_stack[file_index].is_untitled) - { - snprintf(tmp, DEFAULT_BUFFER_X, "%s %s %s -o %s.exe", Tab_stack[file_index].Compilerinfo.path, Tab_stack[file_index].Compilerinfo.flags, FullPath(Tab_stack[file_index].filename), FullPath(Tab_stack[file_index].filename)); - StartProcess(tmp); - WriteLogFile("Started compiler (command line '%s') ", tmp); - } - - break; - case F10: // F10 - StartProcess("explorer.exe ."); // Open current directory in explorer - break; - case SHIFTF5: // S-F5 (Set macro) - PrintBottomString("%s", NEWTRODIT_PROMPT_CREATE_MACRO); - macro_input = TypingFunction(32, 255, MACRO_ALLOC_SIZE, NULL); - if (macro_input[0] == '\0') - { - FunctionAborted(&Tab_stack[file_index], macro_input); - break; - } - - strncpy_n(run_macro, macro_input, MACRO_ALLOC_SIZE); - free(macro_input); - PrintBottomString("%s%s", NEWTRODIT_MACRO_SET, macro_input); - WriteLogFile("%s%s", NEWTRODIT_MACRO_SET, macro_input); - c = -2; - break; - case SHIFTF10: // S-F10 key - StartProcess("cmd.exe"); // Open command prompt - break; - case CTRLF1: // ^F1 key - SetCursorSettings(false, GetConsoleInfo(CURSOR_SIZE)); - ClearPartial(0, 1, XSIZE, YSIZE - 1); - n = strlen_n(join("Contribute at ", newtrodit_repository)); - ptr = ProgInfo(); - if (n < strlen_n(ptr)) - { - n += (strlen_n(ptr) - n); - } - SetColor(fg_color); - ClearPartial((XSIZE / 2) - (n / 2) - 1, (YSIZE / 2) - 3, n + 2, 7); // Create a box - CenterText("About Newtrodit", (YSIZE / 2) - 2); - CenterText(ptr, (YSIZE / 2)); - // I know it's not the best way to do it, but it works - CenterText(join("Contribute at ", newtrodit_repository), (YSIZE / 2) + 2); - free(ptr); - - getch_n(); - ClearPartial(0, 1, XSIZE, YSIZE - 1); - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - SetCursorSettings(true, GetConsoleInfo(CURSOR_SIZE)); - - break; - case CTRLF4: // ^F4 - if (!CheckKey(VK_SHIFT)) - { - - CloseFile(&Tab_stack[file_index]); - PrintBottomString("%s", NEWTRODIT_FILE_CLOSED); - c = -2; - ch = 0; - break; - } - - case ALTF4: // A-F4 key - if (!CheckKey(VK_CONTROL)) - { - QuitProgram(SInf.color); - ShowBottomMenu(); - continue; - } - break; - - /* This one (148) doesn't work on Linux */ - case 148: // ^TAB / S-^TAB (Switch file) - SwitchTab(&Tab_stack[file_index], CheckKey(VK_SHIFT)); - break; - - case ALTHOME: // A-HOME key "smart home" (go to the first non-whitespace character) - _xpos = strspn(Tab_stack[file_index].strsave[_ypos], " \t"); - UpdateHorizontalScroll(&Tab_stack[file_index], true); - - break; - case ALTEND: // A-END key "smart end" (go to the last non-whitespace character) - n = NoLfLen(Tab_stack[file_index].strsave[_ypos]); - - while ((Tab_stack[file_index].strsave[_ypos][n - 1] == ' ' || Tab_stack[file_index].strsave[_ypos][n - 1] == '\t') && Tab_stack[file_index].strsave[_ypos][n - 1] != '\0') - { - n--; - } - - _xpos = n + (TokCount(Tab_stack[file_index].strsave[_ypos], "\t") * TAB_WIDE); - - if (_xpos < 0) - { - _xpos = 0; - } - - break; - } - - ch = 0; - continue; - } - - if (ch == CTRLS) // ^S - { - n = !!CheckKey(VK_SHIFT); - SaveFile(&Tab_stack[file_index], NULL, n); - - ch = 0; - continue; - } - if (ch == CTRLV) // ^V = Paste - { -#ifdef _WIN32 - PasteClipboardNewtrodit(&Tab_stack[file_index]); -#else - PrintBottomString("Pasting from clipboard is not supported in Linux."); - c = -2; -#endif - ch = 0; - continue; - } - - if (ch == CTRLD) // ^D (Debug tool/dev mode) / S-^D = Toggle dev mode - { - ch = 0; - if (CheckKey(VK_SHIFT)) - { - ToggleOption(&devMode, NEWTRODIT_DEV_TOOLS, false); - } - else - { - if (devMode) - { - PrintBottomString("\"%.*s\"", strcspn(Tab_stack[file_index].strsave[_ypos], Tab_stack[file_index].newline), Tab_stack[file_index].strsave[_ypos]); - } - } - - c = -2; - continue; - } - if (ch == CTRLW) // ^W - { - if (!CheckKey(VK_SHIFT)) - { - if ((n = CloseFile(&Tab_stack[file_index])) == 1) - { - PrintBottomString("%s", NEWTRODIT_FILE_CLOSED); - WriteLogFile("%s", NEWTRODIT_FILE_CLOSED); - } - else if (n < 0) - { - PrintBottomString("%s", NEWTRODIT_ERROR_FAILED_CLOSE_FILE); - WriteLogFile("%s", NEWTRODIT_ERROR_FAILED_CLOSE_FILE); - } - - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - c = -2; - ch = 0; - continue; - } - else - { - if (devMode) - { - clock_t st = clock(); - for (int i = 0; i < 1000; i++) - { - RefreshLine(&Tab_stack[file_index], _ypos, Tab_stack[file_index].display_y, false); - } - printf("Syntax h. time taken: %f\n", ((float)(clock() - st)) / CLOCKS_PER_SEC); - syntaxHighlighting = false; - - st = clock(); - for (int i = 0; i < 1000; i++) - { - RefreshLine(&Tab_stack[file_index], _ypos, Tab_stack[file_index].display_y, false); - } - printf("Time taken (No SH): %f\n", ((float)(clock() - st)) / CLOCKS_PER_SEC); - - c = -2; - - /* PrintBottomString("[Dev mode] Are you sure you want to insert junk data? (y/n)"); - if (YesNoPrompt()) - { - memcpy(Tab_stack[file_index].strsave[_ypos] + _xpos, "äº\\&9=)='A€€¨Y¨ª¨ÿ´^^*Ç", 37); - PrintBottomString("Warning: Junk data inserted for dev mode testing."); - } - c = -2; - - ch = 0; - continue; */ - } - } - } - - if (ch == CTRLQ) // ^Q = Quit program ; S-^Q = Count lines of code in files - { - if (!CheckKey(VK_SHIFT)) - { - QuitProgram(SInf.color); - ShowBottomMenu(); - SetColor(bg_color); - ch = 0; - continue; - } - else - { - n = 0; - n2 = 0; - for (int i = 0; i < open_files; i++) - { - n += Tab_stack[i].linecount; - for (int k = 0; k < Tab_stack[i].linecount; k++) - { - n2 += strlen_n(Tab_stack[i].strsave[k]); - } - } - PrintBottomString("%d lines of code (total %d bytes) in %d opened files.", n, n2, open_files); - ch = 0; - c = -2; - continue; - } - } - if (ch == CTRLX) // ^X = Cut - { - - if (useOldKeybindings) - { - if (!CheckKey(VK_SHIFT)) - { - QuitProgram(SInf.color); - ShowBottomMenu(); - SetColor(bg_color); - ch = 0; - } - } - else - { - if (!CheckKey(VK_SHIFT)) - { - if (Tab_stack[file_index].strsave[_ypos][0] != '\0') - { - - /* Tab_stack[file_index].Ustack->size = strlen_n(Tab_stack[file_index].strsave[_ypos]); - Tab_stack[file_index].Ustack->line = malloc(sizeof(char) * (Tab_stack[file_index].Ustack->size + 1)); - memset(Tab_stack[file_index].Ustack->line, 0, Tab_stack[file_index].Ustack->size); - - Tab_stack[file_index].Ustack->line = strdup(Tab_stack[file_index].strsave[_ypos]); - Tab_stack[file_index].Ustack->line_count = _ypos; - Tab_stack[file_index].Ustack->line_pos = _xpos; - Tab_stack[file_index].Ustack->create_nl = false; - Tab_stack[file_index].Ustack++->create_nl = false; */ -#ifdef _WIN32 - SetClipboardNewtrodit(Tab_stack[file_index].strsave[_ypos]); -#endif - memset(Tab_stack[file_index].strsave[_ypos], 0, strlen_n(Tab_stack[file_index].strsave[_ypos])); - ClearPartial((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y, XSIZE - (lineCount ? Tab_stack[file_index].linecount_wide : 0), 1); - _xpos = 0; - } - - ch = 0; - continue; - } - } - - continue; - } - if (ch == CTRLBS) // ^Backspace - { - SelectDelete(&Tab_stack[file_index], true); - if (_xpos > 0) - { - /* Tab_stack[file_index].Ustack->size = strlen_n(Tab_stack[file_index].strsave[_ypos]) + 1; - Tab_stack[file_index].Ustack->line = malloc(Tab_stack[file_index].Ustack->size); - memset(Tab_stack[file_index].Ustack->line, 0, Tab_stack[file_index].Ustack->size); - Tab_stack[file_index].Ustack->line = strdup(Tab_stack[file_index].strsave[_ypos]); // If it's not a duplicate, the value will change - Tab_stack[file_index].Ustack->line_count = _ypos; - Tab_stack[file_index].Ustack->line_pos = _xpos; - Tab_stack[file_index].Ustack->create_nl = false; - Tab_stack[file_index].Ustack->delete_nl = false; */ - n = strlen_n(Tab_stack[file_index].strsave[_ypos]); - bs_tk = TokBackPos(Tab_stack[file_index].strsave[_ypos], "()[]{}\t ", "?!"); - DeleteStr(Tab_stack[file_index].strsave[_ypos], bs_tk, _xpos - bs_tk); - if (n != strlen_n(Tab_stack[file_index].strsave[_ypos])) - { - _xpos = bs_tk; - memset(Tab_stack[file_index].strsave[_ypos] + strlen_n(Tab_stack[file_index].strsave[_ypos]), 0, Tab_stack[file_index].bufx - strlen_n(Tab_stack[file_index].strsave[_ypos])); // Empty the buffer - } - RefreshLine(&Tab_stack[file_index], _ypos, Tab_stack[file_index].display_y, true); - } - else - { - if (_ypos > 1) - { - InsertDeletedRow(&Tab_stack[file_index]); - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - ClearPartial(0, 1, XSIZE, YSIZE - 2); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - Tab_stack[file_index].linecount--; - } - } - - ch = 0; - continue; - } - - if (ch == CTRLY) // ^Y = Redo - { - - /* strncpy_n(undo_stack, Tab_stack[file_index].strsave[undo_stack_line], BUFFER_X); - strncpy_n(Tab_stack[file_index].strsave[undo_stack_line], redo_stack, BUFFER_X); - LoadAllNewtrodit(); - FunctionAborted(&Tab_stack[file_index]); - fflush(stdout); - - ch = 0; - continue;*/ - } - - if (ch == CTRLZ) // ^Z = Undo - { - /* - if (undo_stack_tree > 0) - { - undo_stack_tree--; - _xpos = Tab_stack[file_index].Ustack->line_pos; - _ypos = Tab_stack[file_index].Ustack->line_count; - if (Tab_stack[file_index].Ustack->create_nl == true) - { - InsertDeletedRow(&Tab_stack[file_index]); - } - if (Tab_stack[file_index].Ustack->delete_nl == true) - { - InsertRow(Tab_stack[file_index].strsave, _xpos, _ypos, Tab_stack[file_index].Ustack->line); - Tab_stack[file_index].strsave[Tab_stack[file_index].Ustack->line_count] = strdup(Tab_stack[file_index].Ustack->line); - } - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - ClearPartial(0, Tab_stack[file_index].display_y, XSIZE, YSIZE - Tab_stack[file_index].display_y - 1); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - else - { - Tab_stack[file_index].strsave[_ypos] = Tab_stack[file_index].Ustack->line; - } - if (strlen_n(Tab_stack[file_index].Ustack->line) < strlen_n(Tab_stack[file_index].strsave[ypos])) - { - xpos = NoLfLen(Tab_stack[file_index].Ustack->line); - } - - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), _ypos); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - } - - ch = 0; - continue; - */ - } - - if (ch == CTRLH && !CheckKey(BS) && CheckKey(VK_CONTROL)) // ^H = Replace string / S-^H = Same as F1 (opens help) - { - ch = 0; - - if (CheckKey(VK_SHIFT)) - { - NewtroditHelp(); - - LoadAllNewtrodit(); - - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - DisplayCursorPos(&Tab_stack[file_index]); - } - else - { - ClearPartial(0, YSIZE - 2, XSIZE, 2); - - printf("%.*s\n%.*s", wrapSize, NEWTRODIT_PROMPT_FIND_STRING, wrapSize, NEWTRODIT_PROMPT_REPLACE_STRING); - n = _xpos; - gotoxy(strlen_n(NEWTRODIT_PROMPT_FIND_STRING), YSIZE - 2); - find_string = TypingFunction(32, 255, DEFAULT_ALLOC_SIZE, NULL); - if (find_string[0] == '\0') - { - ClearPartial(0, BOTTOM - 1, XSIZE, Tab_stack[file_index].linecount_wide); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - FunctionAborted(&Tab_stack[file_index], replace_string); - continue; - } - gotoxy(strlen_n(NEWTRODIT_PROMPT_REPLACE_STRING), BOTTOM); - replace_string = TypingFunction(32, 255, DEFAULT_ALLOC_SIZE, NULL); - if (replace_string[0] == '\0') - { - ClearPartial(0, BOTTOM - 1, XSIZE, Tab_stack[file_index].linecount_wide); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - - FunctionAborted(&Tab_stack[file_index], replace_string); - continue; - } - - for (int i = 1; i < Tab_stack[file_index].bufy; i++) // Line 0 is unused - { - replace_str_ptr = ReplaceString(Tab_stack[file_index].strsave[i], find_string, replace_string, &replace_count); - if (replace_str_ptr) - { - if (strlen_n(replace_str_ptr) < strlen_n(Tab_stack[file_index].strsave[i])) - { - _xpos = strlen_n(replace_str_ptr); - } - - strncpy_n(Tab_stack[file_index].strsave[i], replace_str_ptr, BUFFER_X); - } - } - - if (strlen_n(Tab_stack[file_index].strsave[_ypos]) < n) - { - _xpos = strlen_n(Tab_stack[file_index].strsave[_ypos]); - } - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - PrintBottomString("Replaced %d occurrences of '%s'", replace_count, find_string); - c = -2; - ShowBottomMenu(); - DisplayCursorPos(&Tab_stack[file_index]); - if (replace_count > 0) - { - Tab_stack[file_index].is_modified = true; - } - } - continue; - } - -#ifdef _WIN32 - if ((ch == BS && _NEWTRODIT_OLD_SUPPORT == 1) || (ch == BS && CheckKey(BS) && !CheckKey(VK_CONTROL))) // Backspace key (Not Control-H) -#else - if (ch == BS) -#endif - { - SelectDelete(&Tab_stack[file_index], true); - if (_xpos > 0) - { - Tab_stack[file_index].is_modified = true; - _xpos--; - DeleteChar(Tab_stack[file_index].strsave[_ypos], _xpos); - ClearPartial((lineCount ? Tab_stack[file_index].linecount_wide : 0) + (NoLfLen(Tab_stack[file_index].strsave[_ypos])), Tab_stack[file_index].display_y, 2, 1); // Clear the character - gotoxy((lineCount ? Tab_stack[file_index].linecount_wide : 0), Tab_stack[file_index].display_y); - print_line(Tab_stack[file_index].strsave[_ypos], _ypos); - } - else - { - /* Act as END key */ - if (_ypos > 1) - { - Tab_stack[file_index].is_modified = true; - - n = NoLfLen(Tab_stack[file_index].strsave[_ypos - 1]); // Store the length of the line so we can change the X position later - - if (Tab_stack[file_index].strsave[_ypos][0] == '\0' && !LineContainsNewLine(&Tab_stack[file_index], _ypos)) - { - memset(Tab_stack[file_index].strsave[_ypos - 1] + NoLfLen(Tab_stack[file_index].strsave[_ypos - 1]), 0, Tab_stack[file_index].bufx - NoLfLen(Tab_stack[file_index].strsave[_ypos - 1])); - } - else - { - memcpy(Tab_stack[file_index].strsave[_ypos - 1] + NoLfLen(Tab_stack[file_index].strsave[_ypos - 1]), Tab_stack[file_index].strsave[_ypos], NoLfLen(Tab_stack[file_index].strsave[_ypos])); - DeleteRow(Tab_stack[file_index].strsave, _ypos, Tab_stack[file_index].bufy - 1); - } - - Tab_stack[file_index].linecount--; - _xpos = n; // Assign the X position to the old length of the previous line - _ypos--; - - Tab_stack[file_index].linecount--; - if (!UpdateScrolledScreen(&Tab_stack[file_index])) - { - ClearPartial(0, Tab_stack[file_index].display_y, XSIZE, (YSIZE - Tab_stack[file_index].display_y) - 1); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - Tab_stack[file_index].is_modified = true; - - SetDisplayY(&Tab_stack[file_index]); - // printf("Disp Y: %d", Tab_stack[file_index].display_y); - } - } - } - else - { - if (ch != 0) - { - SelectDelete(&Tab_stack[file_index], true); - } - if (ch == TAB && CheckKey(VK_TAB)) // TAB key - { - if (!AutoComplete(&Tab_stack[file_index], _xpos, _ypos)) - { - if (convertTabtoSpaces) - { - ptr = PrintTab(TAB_WIDE); - InsertStr(Tab_stack[file_index].strsave[_ypos], ptr, _xpos, false, Tab_stack[file_index].bufx); - _xpos += TAB_WIDE; - RefreshLine(&Tab_stack[file_index], _ypos, Tab_stack[file_index].display_y, false); - free(ptr); // Free the memory allocated by PrintTab() - } - } - ch = 0; - } - - if (ch > 31) // Printable character - { - Tab_stack[file_index].is_modified = true; - n = -1; - - for (int k = 0; k < sizeof(autocomplete_double) / sizeof(autocomplete_double[0]); k++) - { - if (ch == autocomplete_double[k][0]) - { - n = k; - continue; - } - } - - if (replaceChar || Tab_stack[file_index].strsave[_ypos][_xpos] == '\0') // Insert key not pressed - { - if (n >= 0) - { - memcpy(Tab_stack[file_index].strsave[_ypos] + _xpos, autocomplete_double[n], strlen_n(autocomplete_double[n])); - } - else - { - Tab_stack[file_index].strsave[_ypos][_xpos] = ch; // Add character to buffer - } - - if (_xpos < wrapSize) - { - putchar(ch); - } - } - else - { - if (n >= 0) - { - InsertStr(Tab_stack[file_index].strsave[_ypos], autocomplete_double[n], _xpos, false, Tab_stack[file_index].bufx); - } - else - { - InsertChar(Tab_stack[file_index].strsave[_ypos], ch, _xpos, false, Tab_stack[file_index].bufx); - } - } - - RefreshLine(&Tab_stack[file_index], _ypos, Tab_stack[file_index].display_y, false); // No need to clear the line as it will get overwritten - - _xpos++; - } - else - { - if (ch != 0 && ch <= 26 && ch != ENTER) - { - memset(inbound_ctrl_key, 0, sizeof(inbound_ctrl_key)); // Clear the string for the next key - if (CheckKey(VK_CONTROL)) - { - strcat(inbound_ctrl_key, "Ctrl-"); - } - if (CheckKey(VK_MENU)) - { - strcat(inbound_ctrl_key, "Alt-"); - } - if (CheckKey(VK_SHIFT)) - { - strcat(inbound_ctrl_key, "Shift-"); - } - if (CheckKey(VK_ESCAPE)) - { - strcat(inbound_ctrl_key, "^^["); - } - - inbound_ctrl_key[strlen_n(inbound_ctrl_key)] = ch + 64; // Convert getch return value to ASCII - PrintBottomString(NEWTRODIT_ERROR_INVALID_INBOUND, inbound_ctrl_key); - c = -2; // For later use - } - } - } - - if ((Tab_stack[file_index].strsave[1][0] == '\0' && Tab_stack[file_index].strsave[1][1] == '\0') || Tab_stack[file_index].strsave[1][0] == EOF) // If the document is empty set modified to false - { - Tab_stack[file_index].is_modified = false; - UpdateTitle(&Tab_stack[file_index]); - } - - if (strlen_n(Tab_stack[file_index].strsave[_ypos]) >= Tab_stack[file_index].bufx - (TAB_WIDE * 2) || _ypos > Tab_stack[file_index].bufy || _xpos >= Tab_stack[file_index].bufx - (TAB_WIDE * 2)) // Avoid buffer overflows by resizing the buffer - { - tmp = realloc_n(Tab_stack[file_index].strsave[_ypos], Tab_stack[file_index].bufx, Tab_stack[file_index].bufx + BUFFER_INCREMENT); - Tab_stack[file_index].bufx += BUFFER_INCREMENT; - - if (!tmp) - { - PrintBottomString(NEWTRODIT_ERROR_OUT_OF_MEMORY); - getch_n(); - SaveFile(&Tab_stack[file_index], NULL, false); - ExitRoutine(ENOMEM); - } - else - { - free(Tab_stack[file_index].strsave[_ypos]); - Tab_stack[file_index].strsave[_ypos] = tmp; - } - } - } - - NewtroditCrash("Unexpected program end.", EFAULT); - ExitRoutine(EFAULT); - return 0; + end_editor(); } \ No newline at end of file diff --git a/src/newtrodit.h b/src/newtrodit.h new file mode 100644 index 0000000..31c87c4 --- /dev/null +++ b/src/newtrodit.h @@ -0,0 +1,16 @@ +#ifndef NEWTRODIT_H +#define NEWTRODIT_H + +#include "newtrodit_gui.h" +#include "fileio.h" +#include "input.h" +#include "line.h" + +void sigsegv_handler(); +int init_editor(int argc, char* argv[]); +int end_editor(); +void quit_newtrodit(File *tstack); +int handle_keystrokes(InputUTF8 ch, File *tstack); +int editor_main(); + +#endif // NEWTRODIT_H \ No newline at end of file diff --git a/src/newtrodit_core.c b/src/newtrodit_core.c new file mode 100644 index 0000000..13f26b0 --- /dev/null +++ b/src/newtrodit_core.c @@ -0,0 +1,180 @@ +#include "newtrodit_core.h" + +const int DEFAULT_ALLOC_SIZE = 512; +const size_t LINE_SIZE = 64; +const size_t DEFAULT_ALLOC_LINES = 10; +const size_t LINE_Y_INCREASE = 5; + +size_t utf8len_n(const char *s) +{ + if (!s) + return 0; + return utf8len(s); +} + +size_t utf8nlen_n(const char *s, size_t n) +{ + if (!s) + return 0; + return utf8nlen(s, n); +} + +size_t utf8len_null(const char *s, size_t max_bytes) // Get the number of UTF-8 characters in a non-null terminated string with a known byte length +{ + if (!s) + return 0; + const char *ptr = s; + utf8_int32_t cp = 0; + size_t ulen = 0; + while (*ptr != '\0' && ulen < max_bytes) + { + ptr = utf8codepoint(ptr, &cp); // Get a pointer to the next codepoint + ulen++; // Increase the Unicode character counter + } + return ulen; +} + +size_t strlen_n(const char *s) +{ + if (!s) + return 0; + return strlen(s); +} + +int vt_settings(bool enabled) +{ +#ifdef _WIN32 + DWORD lmode; // Process ANSI escape sequences + + if (!GetConsoleMode(hStdout, &lmode)) + return 0; + if (enabled) + lmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING & ~DISABLE_NEWLINE_AUTO_RETURN; + else + lmode &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + + return SetConsoleMode(hStdout, lmode); +#endif + return 0; +} + +/* Safe version of strcpy() and strncpy() */ +char *strncpy_n(char *dest, const char *src, size_t count) +{ + // Better version that str8ncpy() because it always null terminates strings + + if (count) + { + memset(dest, 0, count); + strncat(dest, src, count); + return dest; + } + return NULL; +} + +size_t strrpbrk(const char *s, const char *find) // Reverse strpbrk, just like strrchr but for multiple characters +{ + size_t findlen = utf8len_n(find); + size_t slen = utf8len_n(s); + for (size_t i = slen; i > 0; i--) + { + for (size_t j = 0; j < findlen; j++) + { + if (s[i - 1] == find[j]) + return i - 1; + } + } + return 0; +} + +void *realloc_n(void *old, size_t old_sz, size_t new_sz) +{ + void *new = malloc(new_sz); + if (!new) + return NULL; + memcpy(new, old, old_sz); + free(old); + return new; +} + +int valid_file_name(const char *filename) +{ + return utf8pbrk(filename, "*?\"<>|\x1b") == NULL; +} + +char *remove_quotes(char *s) +{ + size_t len = strlen_n(s); + if (s[0] == '\"' && s[len - 1] == '\"') + { + memmove(s, s + 1, len - 2); + s[len] = '\0'; + } + return s; +} + +int trim_message(char *msg, size_t max_len) +{ + size_t ulen = utf8len(msg); + if (ulen > max_len) + { + msg[max_len] = '\0'; + return 1; + } + return 0; +} + +int set_status_msg(bool display_once, char *msg, ...) +{ + va_list args; + va_start(args, msg); + + ed.status_msg = calloc(DEFAULT_ALLOC_SIZE + 1, sizeof(utf8_int32_t)); + if (!ed.status_msg) + return 0; + + size_t copyamount = DEFAULT_ALLOC_SIZE * sizeof(utf8_int32_t); + vsnprintf(ed.status_msg, copyamount, msg, args); + trim_message(ed.status_msg, ed.xsize - (fullCursorInfoDisplay ? 45 : 20)); + + ed.dirty = true; + ed.displayStatusOnce = display_once; + return 1; +} + +int clear_status_msg() +{ + if (ed.status_msg != NULL) + free(ed.status_msg); + + ed.status_msg = NULL; + ed.dirty = true; + return 1; +} + +int last_token_position(const char *s, const char *token) +{ + + int lastpos = -1; + if (!s || !token) + return lastpos; + + for (size_t i = 0; i < strlen_n(s); i++) + { + for (size_t j = 0; j < strlen_n(token); j++) + { + if (s[i] == token[j]) + lastpos = i; + } + } + return lastpos; +} + +char *last_token(char *tok, const char *char_token) +{ + if (!tok || !char_token) + return NULL; + + int pos = last_token_position(tok, char_token); + return tok + pos + 1; +} diff --git a/src/newtrodit_core.h b/src/newtrodit_core.h new file mode 100644 index 0000000..2afc36b --- /dev/null +++ b/src/newtrodit_core.h @@ -0,0 +1,170 @@ +#ifndef NEWTRODIT_CORE_H +#define NEWTRODIT_CORE_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/wcwidth.h" +#include "utf8/utf8.h" + +#ifdef _WIN32 +#include +#endif + + +extern const int DEFAULT_ALLOC_SIZE; +extern const size_t LINE_SIZE; +extern const size_t DEFAULT_ALLOC_LINES; + +extern const size_t LINE_Y_INCREASE; + +#if !defined ENABLE_VIRTUAL_TERMINAL_PROCESSING || !defined DISABLE_NEWLINE_AUTO_RETURN +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define DISABLE_NEWLINE_AUTO_RETURN 0x0008 +#endif + +enum file_flags_modifier +{ + IS_MODIFIED = 1, + IS_SAVED = 2, + IS_UNTITLED = 4, + IS_READONLY = 8, +}; + +enum unicode_encodings +{ + ENCODING_UTF8 = 1, + ENCODING_UTF16LE = 2, + ENCODING_UTF16BE = 3, + ENCODING_UTF8BOM = 4, + ENCODING_UTF32LE = 5, + ENCODING_UTF32BE = 6, +}; + +const char newtrodit_version[] = "1.0"; +const char newtrodit_date[] = "2024/09/05"; + +typedef struct Line +{ + char *str; // Buffer that contains the line + char *render; + + size_t bufx; // Allocated line size + size_t render_bufx; // Allocated render size + + size_t len; // Line Length + size_t rlen; // Rendered length + size_t rnlen; // Rendered length without padding + + size_t ulen; // UTF-8 length +} Line; + +typedef struct Position +{ + size_t x; + size_t y; +} Position; + +typedef struct Select +{ + Position start; + Position end; + bool is_selected; +} Select; + +typedef struct File +{ + char *filename; + char *fullpath; + char *language; // Language (e.g: PowerShell, C, C++, etc.) + + // File information + size_t xpos, ypos; + size_t uxpos; // UTF-8 X position + size_t uwxpos; // UTF-8 width position + + Line **line; // Store each line file + size_t alloc_lines; + size_t linecount; + long long size; + + size_t linenumber_wide; + size_t linenumber_padding; + + char *newline; + unsigned int file_flags; + + unsigned int encoding; + size_t encoding_bom_len; + bool post_load_rendering; + + time_t fwrite_time; + time_t fread_time; + Select selection; + Position begin_display; + Position scroll_pos; +} File; // File information. This is used to store all the information about the file. + +typedef struct Editor +{ + size_t xsize, ysize; // Window size + int open_files; // Number of files open + int file_index; // Current file index + + char *log_file_name; + char *status_msg; + bool useLogFile; + bool lineNumbers; + bool dirty; + bool displayStatusOnce; + +} Editor; // Editor information + +typedef struct Color +{ + int r, g, b; + bool background; +} Color; + +Editor ed; +File **file; + +#ifdef _WIN32 +#include "win32/graphics_win32.h" +const char PATHDELIMS[] = "\\/"; // Set the path delimiters for Windows and Linux +#else +#include "linux/graphics_linux.h " +const char PATHDELIMS[] = "/"; +#endif + +#define _xpos file[ed.file_index]->xpos +#define _ypos file[ed.file_index]->ypos +#define _uxpos file[ed.file_index]->uxpos +#define _uwxpos file[ed.file_index]->uwxpos + +size_t utf8len_n(const char *s); +size_t utf8nlen_n(const char *s, size_t max_upos); +size_t utf8len_null(const char *s, size_t max_bytes); // Get the number of UTF-8 characters in a non-null terminated string with a known byte length +size_t strlen_n(const char *s); +int vt_settings(bool enabled); +char *strncpy_n(char *dest, const char *src, size_t count); +size_t strrpbrk(const char *s, const char *find); // Reverse strpbrk, just like strrchr but for multiple characters +void *realloc_n(void *old, size_t old_sz, size_t new_sz); +int valid_file_name(const char *filename); +char *remove_quotes(char *s); +int trim_message(char *msg, size_t max_len); +int set_status_msg(bool display_once, char *msg, ...); +int clear_status_msg(); +int last_token_position(const char *s, const char *token); +char *last_token(char *tok, const char *char_token); + +#endif // NEWTRODIT_CORE_H \ No newline at end of file diff --git a/src/newtrodit_gui.c b/src/newtrodit_gui.c index 58bc957..64ddab4 100644 --- a/src/newtrodit_gui.c +++ b/src/newtrodit_gui.c @@ -1,392 +1,477 @@ -/* - Newtrodit: A console text editor - Copyright (c) 2021-2023 anic17 Software - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see - -*/ - -int SelectPrint(File_info *tstack, size_t yps); - -void TopHelpBar() -{ - int xs = XSIZE; - - SetColor(fg_color); - ClearPartial(0, 0, xs, 1); - printf("%.*s", xs, NEWTRODIT_DIALOG_MANUAL_TITLE); - SetColor(bg_color); - return; -} - -void BottomHelpBar() -{ - SetColor(fg_color); - PrintBottomString(NEWTRODIT_DIALOG_MANUAL); - SetColor(bg_color); - return; -} - -int SetWrapSize() -{ - lineCount ? (wrapSize = XSIZE - 1 - Tab_stack[file_index].linecount_wide) : (wrapSize = XSIZE - 1); - - (wrapSize < 0) ? wrapSize = 0 : wrapSize; // Check if wrapSize is negative - return wrapSize; -} - -int CenterText(char *text, int yps) // Algorithm: (XSIZE / 2) - (len / 2) -{ - if (!text) - { - WriteLogFile("%s", NEWTRODIT_INTERNAL_EXPECTED_NULL); - return 0; - } - SetColor(fg_color); - int center_text = (XSIZE / 2) - (strlen_n(text) / 2); - gotoxy(center_text, yps); - int pos = wrapSize - strlen_n(text); - if (pos < 0) - { - pos = abs(pos); - } - - printf("%.*s", pos, text); - SetColor(bg_color); - return center_text; -} - -int DisplayTabIndex(File_info *tstack) -{ - - if (open_files > 1) - { - int pos = (XSIZE / 2) + (strlen_n(StrLastTok(tstack->filename, PATHTOKENS)) / 2) + (strlen_n(StrLastTok(tstack->filename, PATHTOKENS)) % 2) + 1; // Center text and add a space - gotoxy(pos, 0); - - SetColor(fg_color); - - printf("(%d/%d)", file_index + 1, open_files); - SetColor(bg_color); - } - return 1; -} - -void DisplayFileType() -{ - SetColor(fg_color); - size_t len = strlen_n(Tab_stack[file_index].language); - char *disp_ptr = calloc(len + DEFAULT_ALLOC_SIZE, sizeof(char)); - if (!strcmp(Tab_stack[file_index].language, DEFAULT_LANGUAGE)) - { - strncpy_n(disp_ptr, Tab_stack[file_index].language, len); - } - else - { - snprintf(disp_ptr, len + DEFAULT_ALLOC_SIZE, "File type: %s", Tab_stack[file_index].language); - } - gotoxy(XSIZE - strlen_n(disp_ptr) - 2, 0); // XSIZE - strlen_n(nl_type) - 2 - fputs(disp_ptr, stdout); - free(disp_ptr); - SetColor(bg_color); -} - -void ShowFindMenu() -{ - SetColor(bg_color); - ClearPartial(0, BOTTOM, XSIZE, 1); - - SetColor(fg_color); - fputs("F3", stdout); - SetColor(bg_color); - fputs(": Next occurrence | ", stdout); - SetColor(fg_color); - fputs("F4", stdout); - SetColor(bg_color); - fputs(": Toggle case sensitive | ", stdout); - SetColor(fg_color); - fputs("F5", stdout); - SetColor(bg_color); - fputs(": Only match full words | ", stdout); - SetColor(fg_color); - fputs("ESC", stdout); - SetColor(bg_color); - fputs(": Quit", stdout); - if (!findInsensitive && !matchWholeWord) - { - fputs(" (No modifiers)", stdout); - } - else - { - fputs(" (", stdout); - if (findInsensitive) - { - printf("Case-insensitive"); - } - if (matchWholeWord) - { - if (findInsensitive) - { - fputs(", ", stdout); - } - fputs("Whole words", stdout); - } - fputs(")", stdout); - } -} - -void ShowBottomMenu() -{ - PrintBottomString(NEWTRODIT_DIALOG_BOTTOM_HELP); - return; -} - -void SetCursorSettings(int visible, int size) -{ -#ifdef _WIN32 - HANDLE Cursor = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_CURSOR_INFO info; - info.dwSize = size; - info.bVisible = visible; - SetConsoleCursorInfo(Cursor, &info); -#else - /* info.dwSize = size; - info.bVisible = visible; */ - - // Set cursor size - if (size > 50) - { - printf("\x1B[\x31 q"); // Blinking block - } - else - { - printf("\x1B[\x33 q"); // Blinking underline - } - - // Set cursor visibility - if (visible) - { - printf("\x1B[?25h"); - } - else - { - printf("\x1B[?25l"); - } -#endif -} - -void NewtroditNameLoad() -{ - SetColor(0x70); - ClearPartial(0, 0, XSIZE, 1); - printf(" Newtrodit %s", newtrodit_version); - SetColor(0x07); -} - -void DisplayCursorPos(File_info *tstack) -{ - int cursorvis = GetConsoleInfo(CURSOR_VISIBLE); - SetCursorSettings(false, GetConsoleInfo(CURSOR_SIZE)); - size_t len = strlen_n(NEWTRODIT_DIALOG_BOTTOM_HELP); - ClearPartial(len, BOTTOM, XSIZE - len, 1); - bool linecount_zero = false; - if (fullCursorInfoDisplay) - { - if (!tstack->linecount) - { - linecount_zero = true; - } - size_t linelen = NoLfLen(tstack->strsave[tstack->ypos]); - printf(longPositionDisplay ? "Line %d/%d (%d%%), Column %d/%d (%d%%)" : "Ln %d/%d (%d%%), Col %d/%d (%u%%)", tstack->ypos, tstack->linecount + linecount_zero, 100 * tstack->ypos / (tstack->linecount + linecount_zero), tstack->xpos + 1, linelen + 1, (size_t)100 * (tstack->xpos) / (!linelen ? 1 : linelen)); - } - else - { - printf(longPositionDisplay ? "Line %d, Column %d" : "Ln %d, Col %d", tstack->ypos, tstack->xpos + 1); // +1 because it's zero indexed - } - SetCursorSettings(cursorvis, GetConsoleInfo(CURSOR_SIZE)); -} - -void LoadAllNewtrodit() -{ - SetCursorSettings(false, GetConsoleInfo(CURSOR_SIZE)); // Hide cursor to reduce flickering - SetColor(bg_color); - SetWrapSize(); - - switch (clearAllBuffer) - { - case 0: - ClearScreen(); - break; - case 1: - ClearPartial(0, 0, XSIZE, YSIZE); - break; - default: // We will be using this when we want a full screen refresh from outside the load function - break; - } - NewtroditNameLoad(); - CenterText(StrLastTok(Tab_stack[file_index].filename, PATHTOKENS), 0); - DisplayTabIndex(&Tab_stack[file_index]); - DisplayFileType(); - ShowBottomMenu(); - - if (lineCount) - { - DisplayLineCount(&Tab_stack[file_index], YSIZE - 3, 1); - } - SetCursorSettings(true, GetConsoleInfo(CURSOR_SIZE)); - - gotoxy(0, 1); -} - -void NewtroditCrash(char *crash_reason, int crash_retval) -{ - signal(SIGSEGV, SIG_DFL); // Reset signal handler to avoid infinite crash loops - int get_le = 0; - - char *crash_desc; - -#ifdef _WIN32 - get_le = GetLastError(); - - crash_desc = (LPSTR)GetErrorDescription(get_le); - -#else - get_le = errno; - crash_desc = (char *)GetErrorDescription(get_le); - -#endif - putchar('\a'); - crash_desc[strcspn(crash_desc, "\r")] = 0; - // LoadAllNewtrodit(); // Just to not have a blank screen and make it scary - int errno_temp = errno; - if (!errno_temp) - { - errno_temp = crash_retval; - } - ClearPartial(0, 1, XSIZE, YSIZE - 1); - char *buf = calloc(DEFAULT_ALLOC_SIZE, sizeof(char)); - snprintf(buf, DEFAULT_ALLOC_SIZE * sizeof(char), "Newtrodit ran into a problem and it crashed. We're sorry.\nPlease report this issue to %s/issues\n\nDebug info:\nerrno: 0x%x (%s)\nGetLastError: 0x%x (%s)\nLast known debug information: %s\n\nProgram information:\nVersion: %s\nBuild date: %s\nCommand line arguments:\n", newtrodit_repository, errno_temp, strerror(errno_temp), get_le, crash_desc, last_known_exception, newtrodit_version, newtrodit_build_date); - fputs(buf, stdout); - WriteLogFile("%s", buf); - for (int i = 0; i < SInf.argc; i++) - { - printf("%s ", SInf.argv[i]); // Prints the command line arguments - } - - snprintf(buf, DEFAULT_ALLOC_SIZE * sizeof(char), "\n\nEditing file '%s', line %d, column %d\nTabs open: %d, current tab: %d\n\nReason: %s\n\nPress enter to exit...\n", Tab_stack[file_index].filename, Tab_stack[file_index].ypos, Tab_stack[file_index].xpos + 1, open_files, file_index + 1, crash_reason); - fputs(buf, stdout); - WriteLogFile("%s", buf); - DisplayCursor(true); - getchar(); - ExitRoutine(crash_retval); // Don't perform any cleanup, just exit -} - -int QuitProgram(int color_quit) -{ - if (Tab_stack[file_index].is_modified) // Second condition should never happen - { - PrintBottomString("%s", NEWTRODIT_PROMPT_SAVE_MODIFIED_FILE); - if (YesNoPrompt()) - { - SaveFile(&Tab_stack[file_index], NULL, false); - } - } - - PrintBottomString("%s", NEWTRODIT_PROMPT_QUIT); - - if (YesNoPrompt()) - { - SetColor(color_quit); - ClearScreen(); - SetCursorSettings(true, CURSIZE); - RestoreConsoleBuffer(); - ExitRoutine(0); - } - else - { - SetColor(bg_color); - } - - return 0; -} - -void UpdateTitle(File_info *tstack) -{ - if (tstack->is_saved) - { - tstack->fullpath = strdup(FullPath(tstack->filename)); - } - if (tstack->is_modified) - { - SetTitle("Newtrodit - %s (Modified)", (fullPathTitle && tstack->is_saved && !tstack->is_untitled) ? tstack->fullpath : tstack->filename); - } - else - { - SetTitle("Newtrodit - %s", (fullPathTitle && tstack->is_saved && !tstack->is_untitled) ? tstack->fullpath : tstack->filename); - } -} - -void print_line(char *line, size_t yps) -{ - - size_t linelen = NoLfLen(line); - - /* TODO: Add proper selection - - SelectPrint(&Tab_stack[file_index], yps); - */ - printf("%.*s", (linelen > wrapSize) ? wrapSize : linelen, line + Tab_stack[file_index].display_x); - - if (syntaxHighlighting && !syntaxAfterDisplay) - { - color_line(line, Tab_stack[file_index].display_x, 0, yps); - } -} - -int ToggleOption(int *option, char *text, int reloadScreen) -{ - *option ^= 1; - - if (reloadScreen) - { - LoadAllNewtrodit(); - DisplayFileContent(&Tab_stack[file_index], stdout, 0); - } - - if (*option) - { - PrintBottomString("%s%s", text, NEWTRODIT_DIALOG_ENABLED); - WriteLogFile("%s%s", text, NEWTRODIT_DIALOG_ENABLED); - } - else - { - PrintBottomString("%s%s", text, NEWTRODIT_DIALOG_DISABLED); - WriteLogFile("%s%s", text, NEWTRODIT_DIALOG_DISABLED); - } - return *option; -} - -void RefreshLine(File_info *tstack, size_t line_num, size_t disp_y, bool clearLine) -{ - if (clearLine) - { - ClearPartial(lineCount ? tstack->linecount_wide : 0, disp_y, wrapSize, 1); - } - else - { - gotoxy(lineCount ? tstack->linecount_wide : 0, disp_y); - } - - print_line(tstack->strsave[line_num], line_num); -} \ No newline at end of file +#include "newtrodit_gui.h" + +/* void TopHelpBar() +{ + SetColor(fg_color); + ClearPartial(0, 0, ed.xsize, 1); + printf("%.*s", ed.xsize, NEWTRODIT_DIALOG_MANUAL_TITLE); + SetColor(bg_color); + return; +} + +void BottomHelpBar() +{ + SetColor(fg_color); + PrintBottomString(NEWTRODIT_DIALOG_MANUAL); + SetColor(bg_color); + return; +} + */ + +void newtrodit_name_load() +{ + + set_color(title_font_color); + set_color(title_bg_color); + clear_partial(0, 0, ed.xsize, 1); + printf(" Newtrodit %s\n", newtrodit_version); +} + +int center_text(char *text, int yps) // Algorithm: (ed.xsize / 2) - (len / 2) +{ + + int center_text = (ed.xsize / 2) - (utf8len_n(text) / 2); + gotoxy(center_text, yps); + int pos = wrapSize - utf8len_n(text); + if (pos < 0) + pos = abs(pos); + + printf("%.*s", pos, text); + return center_text; +} + +void display_file_type(File *tstack) +{ + int x = ed.xsize - utf8len_n(tstack->language) - 2; + gotoxy(x, 0); + fputs(tstack->language, stdout); +} + +void display_bottom_bar(char *status) +{ + gotoxy(0, ed.ysize - 1); + printf("%s | ", (status != NULL) ? status : NEWTRODIT_DIALOG_BOTTOM_HELP); +} + +void print_message(const char *str, ...) +{ + + va_list args; + va_start(args, str); + char *printbuf = calloc(ed.xsize + 1, sizeof(char)); + vsnprintf(printbuf, ed.xsize + 1, str, args); + clear_partial(0, ed.ysize - 1, ed.xsize, 1); + gotoxy(0, ed.ysize - 1); + printf(str, printbuf); + // printf("%.*s", ed.xsize, printbuf); // Don't get out of the buffer + free(printbuf); + va_end(args); + return; +} + + +void display_status(File *tstack, char *status_msg) +{ + display_bottom_bar(status_msg); + display_cursor_pos(tstack, status_msg); +} + +void function_aborted(File *tstack, char *status_msg) +{ + print_message(NEWTRODIT_FUNCTION_ABORTED); + getch_n(); + display_status(tstack, status_msg); + return; +} + +int set_display_pos(File *tstack) +{ + if (tstack->xpos - (ed.lineNumbers ? (tstack->linenumber_wide + tstack->linenumber_padding) : 0) > ed.xsize - 1) + tstack->begin_display.x = tstack->xpos - (ed.lineNumbers ? (tstack->linenumber_wide + tstack->linenumber_padding) : 0) - (ed.xsize - 1); + else + tstack->begin_display.x = 0; + + if (tstack->ypos >= ed.ysize - 3) + { + tstack->begin_display.y = tstack->ypos - ed.ysize + 4; // When reaching the penultimate row, start scrolling + } + else + { + tstack->begin_display.y = 0; + tstack->scroll_pos.y = tstack->ypos; + } + + tstack->scroll_pos.x = 0; + return 0; +} + +void display_cursor_pos(File *tstack, char *status) +{ + size_t len = 3; // Not zero because it already accounts for the " | " separator between position and status message + if (!status) + len += utf8len_n(NEWTRODIT_DIALOG_BOTTOM_HELP); + else + len += utf8len_n(status); + + clear_partial(len, ed.ysize, ed.xsize - len, 1); + + if (fullCursorInfoDisplay) + printf(longPositionDisplay ? "Line %zu/%zu (%zu%%), Column %zu/%zu (%zu%%)" : "Ln %zu/%zu (%zu%%), Col %zu/%zu (%zu%%)", tstack->ypos, tstack->linecount + !tstack->line[tstack->linecount]->len, 100 * tstack->ypos / (tstack->linecount + !tstack->line[tstack->linecount]->len), tstack->xpos + 1, tstack->line[_ypos]->len + 1, (size_t)100 * (tstack->xpos) / (!tstack->line[_ypos]->len ? 1 : tstack->line[_ypos]->len)); + else + printf(longPositionDisplay ? "Line %zu, Column %zu" : "Ln %zu, Col %zu", tstack->ypos, tstack->xpos + 1); // +1 because it's zero indexed + + /* if (fullCursorInfoDisplay) + printf(longPositionDisplay ? "Line %zu/%zu (%zu%%), Column %zu/%zu (%zu%%)" : "Ln %zu/%zu (%zu%%), Col %zu/%zu (%zu%%)", tstack->ypos, tstack->linecount + !tstack->line[tstack->linecount]->len, 100 * tstack->ypos / (tstack->linecount + !tstack->line[tstack->linecount]->len), tstack->uxpos + 1, tstack->line[_ypos]->ulen + 1, (size_t)100 * (tstack->uxpos) / (!tstack->line[_ypos]->ulen ? 1 : tstack->line[_ypos]->ulen)); + else + printf(longPositionDisplay ? "Line %zu, Column %zu" : "Ln %zu, Col %zu", tstack->ypos, tstack->uxpos + 1); // +1 because it's zero indexed + */ +} + + +void load_line_numbering(File *tstack) +{ + if (ed.lineNumbers) + { + set_color(line_number_bg_color); + set_color(line_number_font_color); + clear_partial(0, 1, tstack->linenumber_wide, tstack->scroll_pos.y); + for (size_t i = tstack->begin_display.y; i < tstack->begin_display.y + tstack->scroll_pos.y; i++) + { + printf("%zu", i + 1); + if (i < tstack->begin_display.y + tstack->scroll_pos.y - 1) + putchar('\n'); + } + set_color(bg_color); + set_color(fg_color); + } +} + +void display_line_numbering(File *tstack, size_t ypos) +{ + if (ed.lineNumbers) + { + if ((size_t)(log10(_ypos) + 1) >= tstack->linenumber_wide) + { + tstack->linenumber_wide = (size_t)(log10(_ypos) + 2); + load_line_numbering(tstack); + } + set_color(line_number_bg_color); + set_color(line_number_font_color); + clear_partial(0, tstack->scroll_pos.y, tstack->linenumber_wide, 1); + printf("%zu", ypos); + set_color(bg_color); + set_color(fg_color); + } +} + +void load_all_newtrodit(File *tstack, char *status) +{ + clear_screen(); + set_display_pos(tstack); + newtrodit_name_load(); + center_text(tstack->filename, 0); + display_file_type(tstack); + set_color(fg_color); + set_color(bg_color); + display_status(tstack, status); + load_line_numbering(tstack); + gotoxy(tstack->linenumber_wide + tstack->linenumber_padding, 1); +} + +void set_cursor_settings(int visible, int size) +{ +#ifdef _WIN32 + HANDLE Cursor = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_CURSOR_INFO info; + info.dwSize = size; + info.bVisible = visible; + SetConsoleCursorInfo(Cursor, &info); +#else + /* info.dsize = size; + info.bVisible = visible; */ + + // Set cursor size + if (size > 50) + { + printf("\x1B[\x31 q"); // Blinking block + } + else + { + printf("\x1B[\x33 q"); // Blinking underline + } + + // Set cursor visibility + if (visible) + { + printf("\x1B[?25h"); + } + else + { + printf("\x1B[?25l"); + } +#endif +} + +/* void UpdateTitle(File *tstack) +{ + if (tstack->is_saved) + { + tstack->fullpath = utf8dup(FullPath(tstack->filename)); + } + if (tstack->is_modified) + { + SetTitle("Newtrodit - %s (Modified)", (fullPathTitle && tstack->is_saved && !tstack->is_untitled) ? tstack->fullpath : tstack->filename); + } + else + { + SetTitle("Newtrodit - %s", (fullPathTitle && tstack->is_saved && !tstack->is_untitled) ? tstack->fullpath : tstack->filename); + } +} */ + +int yes_no_prompt() +{ + while (1) + { + switch (tolower(getch_n())) + { + case 'y': + return 1; + break; + case 'n': + return 0; + break; + default: + continue; + } + } +} + +int display_line(File *tstack, size_t linenum, size_t rcount) +{ + gotoxy(file[ed.file_index]->linenumber_wide + file[ed.file_index]->linenumber_padding, tstack->scroll_pos.y); + fwrite(tstack->line[linenum]->render, rcount < tstack->line[linenum]->rlen ? rcount : tstack->line[linenum]->rlen, sizeof(char), stdout); + return 0; +} + +int set_scroll(File *tstack) +{ + tstack->begin_display.y = tstack->ypos - tstack->scroll_pos.y; + if (tstack->begin_display.y < 0) + tstack->begin_display.y = 0; + if (tstack->begin_display.y > tstack->linecount - tstack->scroll_pos.y) + tstack->begin_display.y = tstack->linecount - tstack->scroll_pos.y; + if(tstack->begin_display.y == 0 && tstack->ypos < ed.ysize - 3) + { + tstack->scroll_pos.y = tstack->ypos; + } + return 0; +} + +int increase_scroll(File *tstack, size_t amount) +{ + if (tstack->scroll_pos.y + amount < tstack->linecount) + { + tstack->scroll_pos.y += amount; + } + if (tstack->scroll_pos.y >= ed.ysize - 3) + { + tstack->scroll_pos.y = ed.ysize - 3; + tstack->begin_display.y = tstack->linecount - tstack->scroll_pos.y - ed.ysize + 3; + } + + set_scroll(tstack); + return 0; +} + +int decrease_scroll(File *tstack, size_t amount) +{ + if (tstack->scroll_pos.y - amount >= 0) + { + tstack->scroll_pos.y -= amount; + } + if(tstack->scroll_pos.y <= 3) + { + tstack->begin_display.y -= tstack->scroll_pos.y; + } + set_scroll(tstack); + return 0; +} + +int toggle_option(int *option, char *msg) +{ + *option ^= 1; + set_status_msg(false, "%s%s", msg, *option ? NEWTRODIT_DIALOG_ENABLED : NEWTRODIT_DIALOG_DISABLED); + return *option; +} + +/* char *TypingFunction(int min_ascii, int max_ascii, int max_len, char *oldbuf) +{ + int chr = 0, index = 0; + char *num_str = calloc(max_len + 1, sizeof(utf8_int32_t)); + int startx = 0, starty = ed.ysize-1, orig_cursize = GetConsoleInfo(CURSOR_SIZE); + bool overwrite_mode = false; + size_t cblen = 0, n = -1; + char *clipboard = NULL; + + while (chr != ENTER) // Loop while enter isn't pressed + { + chr = getch_n(); + if (chr == ESC) + { + memset(num_str, 0, max_len); // Empty the string + break; + } + if (chr == BS) // Backspace + { + if (index > 0) + { + DeleteChar(num_str, --index); + ClearPartial(startx + index, starty, (startx + strlen(num_str) - index) >= XSIZE ? (XSIZE - startx - index) : startx + strlen(num_str) - index, 1); + printf("%s", num_str + index); + gotoxy(startx + index, starty); + } + continue; + } + + if (chr == CTRLC) // ^C (Copy to clipboard) + { + SetClipboardNewtrodit(num_str); + continue; + } + if (chr == CTRLV) // ^V (Paste from clipboard) + { + clipboard = GetClipboardNewtrodit(&cblen, true); // Distinguish it from the WinAPI one + if (clipboard) + { + n = 0; + while (clipboard[n] >= min_ascii && clipboard[n] <= max_ascii && n < cblen) // Only count until valid characters are found + { + n++; + } + if (strlen_n(num_str) + n <= max_len && n > 0) + { + num_str = InsertStr(num_str, clipboard, n, false, max_len); + gotoxy(startx, starty); + fputs(num_str, stdout); + index += n; + } + } + else + { + printf("\a"); + } + continue; + } + + if (chr & BIT_ESC0) + { + switch (chr & ~(BIT_ESC0)) + { + case ALTF4: + + QuitProgram(SInf.color); + break; + default: + break; + } + } + if (chr & BIT_ESC224) // Special keys: 224 (0xE0) + { + switch (chr & (~BIT_ESC224)) + { + case LEFT: + if (index > 0) + { + putchar('\b'); + index--; + } + break; + case RIGHT: + if (index < max_len && num_str[index] != '\0') + { + putchar(num_str[index++]); + } + break; + case UP: + if (oldbuf != NULL) + { + memset(num_str, 0, max_len + 1); + index = strlen_n(oldbuf); + memcpy(num_str, oldbuf, index); + + ClearPartial(startx, starty, (startx + strlen(num_str)) >= XSIZE ? (XSIZE - startx) : startx + strlen(num_str), 1); + fputs(num_str, stdout); + gotoxy(startx + index, starty); + } + case DEL: + if (index < max_len) + { + DeleteChar(num_str, index); + ClearPartial(startx, starty, (startx + strlen(num_str)) >= XSIZE ? (XSIZE - startx) : startx + strlen(num_str), 1); + fputs(num_str, stdout); + gotoxy(startx + index, starty); + } + break; + case INS: + overwrite_mode = !overwrite_mode; + SetCursorSettings(true, overwrite_mode ? CURSIZE_INS : CURSIZE); + break; + case HOME: + index = 0; + gotoxy(startx, starty); + break; + case END: + index = strlen_n(num_str); + if (startx + index < XSIZE) + { + gotoxy(startx + index, starty); + } + break; + default: + break; + } + continue; + } + if (chr >= min_ascii && chr <= max_ascii && chr != 0 && ((!overwrite_mode && (strlen(num_str) < max_len && index <= max_len)) || (overwrite_mode && (strlen(num_str) <= max_len && index < max_len)))) // Check if character is a between the range + { + if (overwrite_mode || index >= strlen(num_str)) + { + num_str[index++] = chr; + putchar(chr); + } + else + { + + InsertChar(num_str, chr, index++, false, max_len); + gotoxy(startx, starty); + fputs(num_str, stdout); + gotoxy(startx + index, starty); + } + } + else + { + if (chr != ENTER) + { + putchar('\a'); + } + } + } + SetCursorSettings(cursor_visible, orig_cursize); + bool corrupted = false; + if (max_ascii > 0x7f) + for (int i = 0; i < strlen_n(num_str); i++) + { + if (num_str[i] < min_ascii || num_str[i] > max_ascii) + { + corrupted = true; + num_str[i] = min_ascii; + } + } + if (corrupted) + { + PrintBottomString("Warning: Possible stack corruption detected. Please report this issue."); + getch_n(); + } + return num_str; +} + */ diff --git a/src/newtrodit_gui.h b/src/newtrodit_gui.h new file mode 100644 index 0000000..66a9ac5 --- /dev/null +++ b/src/newtrodit_gui.h @@ -0,0 +1,39 @@ +#ifndef NEWTRODIT_GUI_H +#define NEWTRODIT_GUI_H +#include +#include "newtrodit_core.h" +#include "input.h" + +Color title_bg_color = {.r = 204, .g = 204, .b = 204, .background = true}; +Color title_font_color = {.r = 12, .g = 12, .b = 12, .background = false}; + +Color fg_color = {.r = 204, .g = 204, .b = 204, .background = false}; +Color bg_color = {.r = 12, .g = 12, .b = 12, .background = true}; + +Color line_number_bg_color = {.r = 224, .g = 130, .b = 80, .background = true}; +Color line_number_font_color = {.r = 30, .g = 30, .b = 30, .background = false}; + +/* void print_message(const char *str, ...); +int save_file(File *tstack, char *savefile, bool saveDialog); +int getch_n(); */ +void newtrodit_name_load(); +int center_text(char *text, int yps); +void display_file_type(File *tstack); +void display_bottom_bar(char *status); +void print_message(const char *str, ...); +void display_status(File *tstack, char *status_msg); +void function_aborted(File *tstack, char *status_msg); +int set_display_pos(File *tstack); +void display_cursor_pos(File *tstack, char *status); +void load_line_numbering(File *tstack); +void display_line_numbering(File *tstack, size_t ypos); +void load_all_newtrodit(File *tstack, char *status); +void set_cursor_settings(int visible, int size); +int yes_no_prompt(); +int display_line(File *tstack, size_t linenum, size_t rcount); +int set_scroll(File *tstack); +int increase_scroll(File *tstack, size_t amount); +int decrease_scroll(File *tstack, size_t amount); +int toggle_option(int *option, char *msg); + +#endif diff --git a/src/unicode.c b/src/unicode.c new file mode 100644 index 0000000..8bd5a92 --- /dev/null +++ b/src/unicode.c @@ -0,0 +1,449 @@ +#include "unicode.h" + +int get_file_encoding(char *header, size_t header_size, size_t *p_bom_len) +{ + char utf16le_bom[2] = {0xff, 0xfe}; // UTF-16LE (Little endian) BOM + char utf16be_bom[2] = {0xfe, 0xff}; // UTF-16BE (Big Endian) BOM + char utf32le_bom[4] = {0xff, 0xfe, 0x00, 0x00}; + char utf32be_bom[4] = {0x00, 0x00, 0xfe, 0xff}; + char utf8_bom[3] = {0xef, 0xbb, 0xbf}; // UTF-8 BOM is not recommended for use but we'll handle it anyways + *p_bom_len = 0; + + if (header_size < 2) + return ENCODING_UTF8; + if (!memcmp(header, utf16le_bom, 2)) + { + *p_bom_len = 2; + return ENCODING_UTF16LE; + } + if (!memcmp(header, utf16be_bom, 2)) + { + *p_bom_len = 2; + return ENCODING_UTF16BE; + } + if (header_size < 3) + return ENCODING_UTF8; + if (!memcmp(header, utf8_bom, 3)) + { + *p_bom_len = 3; + return ENCODING_UTF8BOM; + } + if (header_size < 4) + return ENCODING_UTF8; + if (!memcmp(header, utf32le_bom, 4)) + { + *p_bom_len = 4; + return ENCODING_UTF32LE; + } + if (!memcmp(header, utf32be_bom, 4)) + { + *p_bom_len = 4; + return ENCODING_UTF32BE; + } + return ENCODING_UTF8; +} + +size_t codepoint_len(int32_t value) +{ + if (value == 0) // Added check for value == 0 + return 1; + size_t len = 0; + for (int shift = 24; shift >= 0; shift -= 8) + { + if (((value >> shift) & 0xFF)) + len++; + } + return len; +} + +int valid_position(char *s, size_t pos) +{ + utf8_int32_t cp = 0; + utf8codepoint(s + pos, &cp); + return (unsigned int)cp < 0x10FFFFu; // a UTF-8 codepoint cannot be greater than U+10FFFF +} + +size_t utf8wnlen(char *s, size_t max_upos) +{ + if (!s) + return 0; + char *ptr = s; + utf8_int32_t cp; + size_t wlen = 0, ulen = 0; + int cwidth = 0; + while (*ptr != '\0' && ulen < max_upos) + { + ptr = utf8codepoint(ptr, &cp); + ulen++; + cwidth = mk_wcwidth(cp); + if (cwidth < 0) + cwidth = 0; + wlen += cwidth; + } + return wlen; +} + +size_t utf8wlen(char *s) +{ + return utf8wnlen(s, (size_t)(-1)); // -1 returns the max size of a size_t +} + +size_t correct_position(Line *line, size_t *xpos, size_t *uxpos, size_t *uwxpos) +{ + if (!valid_position(line->str, *xpos)) + { + set_status_msg(true, NEWTRODIT_ERROR_INVALID_UNICODE_POSITION); + utf8_int32_t cp = 0; + char *backptr = utf8rcodepoint(line->str + *xpos, &cp); + *xpos -= (&line->str[*xpos] - backptr); // Go back to the previous UTF-8 codepoint + *uxpos = utf8nlen_n(line->str, *xpos); // Set the new UTF-8 position + *uwxpos = utf8wlen(line->str); + } + return *xpos; +} + +char *int32_to_char_array(utf8_int32_t value, char *output) +{ + // Extract each byte from the int32_t and assign it to the char array + size_t i = 0, val = 0; + for (int shift = 24; shift >= 0; shift -= 8) + { + if ((val = ((value >> shift) & 0xFF))) + output[i++] = val; + } + output[4] = '\0'; + // printf("\nString: '%s'", output); + + return output; +} + +int codepoint_width(const char *utf8_char, utf8_int32_t val) +{ // This function can take either a char string or a UTF-8 int32 code + + char utf8s[5] = {0}; + + utf8_int32_t uc = 0; + if (!utf8_char) + { + int32_to_char_array(val, utf8s); + utf8codepoint(utf8s, &uc); + } + else + { + utf8codepoint(utf8_char, &uc); + } + + int cwidth = mk_wcwidth(uc); + if (cwidth < 0) + cwidth = 0; + return cwidth; // Returns 1 for narrow, 2 for wide, or 0 if not printable/blank space +} + +size_t next_char_uwlen(const char *s, size_t xps) +{ + utf8_int32_t uc = 0; + utf8codepoint(s + xps, &uc); + set_status_msg(false, "[U+%04x '%c']", uc, uc); + + return codepoint_width(NULL, uc); +} + +size_t previous_char_uwlen(const char *s, size_t xps) +{ + utf8_int32_t uc = 0; + utf8rcodepoint(s + xps, &uc); + set_status_msg(false, "{U+%04x '%c'}", uc, uc); + + return codepoint_width(NULL, uc); +} + +size_t next_char_xpos(const char *s, size_t *xpos) +{ + utf8_int32_t cp = 0; + if (*xpos > 0) + { + char *next_ptr = utf8codepoint(s + *xpos, &cp); + set_status_msg(true, "Increase: %zu, 0x%04x", next_ptr - s, cp); + *xpos = next_ptr - s; + return *xpos; + } + return 0; +} + +size_t previous_char_xpos(const char *s, size_t *xpos) +{ + utf8_int32_t cp = 0; + if (*xpos > 0) + { + char *prev_ptr = utf8rcodepoint(s + *xpos, &cp); + *xpos = prev_ptr - s; + /* printf("pdif: %zu", (prev_ptr-s)); + getch(); */ + return *xpos; + } + return 0; +} + +size_t utf8getxpos(char *s, size_t uxpos) +{ + char *ptr = s; + utf8_int32_t out_cp = 0; + for (size_t i = 0; i < uxpos; i++) // Loop over the UTF-8 string + { + if (!valid_position(s, ptr - s)) + return 0; + + ptr = utf8rcodepoint(ptr, &out_cp); + } + return ptr - s; +} + +char *insert_utf8_char(Line *line, int32_t value, size_t index) +{ + if (line->len + sizeof(int32_t) > line->bufx - 1) + increase_line(line, LINE_SIZE); + + /* size_t len_check[4] = {0}; + for (size_t i = 0; i < 4; i++) + { + len_check[i] = utf8nlen_n(line->str, (index + i) > line->len ? line->len : (index + i)); + } */ + if (!valid_position(line->str, index)) + { + set_status_msg(true, NEWTRODIT_ERROR_INVALID_UNICODE_POSITION); + return NULL; + } + // correct_position(line, index, utf8nlen_n(line->str, index)); + char *insert_str_utf8 = calloc(sizeof(int32_t) + 1, 1); + int32_to_char_array(value, insert_str_utf8); + /* printf("[%s] %zu;", insert_str_utf8, index); + getch(); */ + size_t len = strlen_n(insert_str_utf8); + + memmove(line->str + index + len, line->str + index, line->len - index); + memcpy(line->str + index, insert_str_utf8, len); + line->str[line->len + len] = '\0'; + line->len += len; + line->ulen++; + free(insert_str_utf8); + return line->str; +} + +char *insert_str(Line *line, char *s2, size_t pos) +{ + size_t l2 = strlen_n(s2); + if (line->len + l2 > line->bufx - 1) + increase_line(line, l2); + + memmove(line->str + pos + l2, line->str + pos, line->len); + memcpy(line->str + pos, s2, l2); + line->str[line->len + l2] = '\0'; + line->len = strlen_n(line->str); // Recalculate the length in case it is wrong to avoid error accumulation + + return line->str; +} + +char *delete_str(Line *line, size_t pos, size_t count) +{ + if (pos + count > line->len) + return NULL; + + memmove(line->str + pos, line->str + pos + count, line->len - (pos + count)); + line->str[line->len - count] = '\0'; + line->len -= count; + return line->str; +} + +char *delete_char(Line *line, size_t pos) +{ + if (line->len > pos) + { + memmove(line->str + pos, line->str + pos + 1, line->len - pos); + line->str[--line->len] = '\0'; + } + return line->str; +} + +/* char *insert_utf8_string(Line *line, char *s, size_t index) +{ + if (!valid_position(line)) + if (line->len + strlen_n(s) > line->bufx - 1) + { + increase_line(line, strlen_n(s)); + } + size_t next_index = index; +} + */ +int display_char(int32_t chr, FILE *fp) +{ + char outbuf[5] = {0}; + int32_to_char_array(chr, outbuf); + return fprintf(fp, "%s", outbuf); +} + +uint16_t swap_le_be(uint16_t num) +{ + return (num >> 8) | (num << 8); +} + +int is_utf16(const unsigned char *s, size_t len, size_t file_len) // Try to detect whether string of text without BOM is UTF-16, and try to guess if it's BE (wchar_t default) or LE (Windows default) +{ + if (len % 2 || file_len % 2) + return false; // Length must be even for UTF-16 encoding + + size_t zero_count_be = 0, zero_count_le = 0; + size_t difference_le = 0, difference_be = 0; + + // Check for zero bytes and calculate differences for endianness determination + for (size_t i = 0; i < len; i += 2) + { + if (s[i] == '\0') + zero_count_be++; + + if (i + 1 < len && (unsigned char)(s[i] - s[i + 1]) > 0x60) // 0x60 comes from 0x80 (first non-ASCII character) - 0x20 (space, first printable ASCII character) + difference_be++; + + if (s[i + 1] == '\0') + zero_count_le++; + + if (i + 1 < len && (unsigned char)(s[i + 1] - s[i]) > 0x60) + difference_le++; + } + + // Check if all characters are zero, which is invalid + if (zero_count_be + zero_count_le == len) + return false; + + bool is_bigendian = zero_count_be >= zero_count_le; + + // Check for unusual patterns that suggest non-UTF-16 text + if ((is_bigendian && (double)difference_be / len < 0.125) || (!is_bigendian && (double)difference_le / len < 0.125)) + return false; + + if ((is_bigendian && (double)zero_count_be / len < 0.125) || (!is_bigendian && (double)zero_count_le / len < 0.125)) + return false; + + // Verify UTF-16 validity by checking surrogate pairs and code point ranges + for (size_t i = 0; i < len;) + { + utf8_int32_t utfchar = 0, surrogate = 0; + + utfchar = (s[i] << 8) | s[i + 1]; + i += 2; + + // Convert endian if needed + if (!is_bigendian) + utfchar = swap_le_be(utfchar); + + // Check if it's a high surrogate + if (utfchar >= 0xD800 && utfchar <= 0xDBFF) + { + if (i + 2 > len) + return -1; + + surrogate = (s[i] << 8) | s[i + 1]; + i += 2; + + if (!is_bigendian) + surrogate = swap_le_be(surrogate); + + if (surrogate < 0xDC00 || surrogate > 0xDFFF) + return -1; + } + + // Check if character is above valid Unicode range + if (utfchar > 0x10FFFF) + return -1; + } + + // Return 2 for BE, 1 for LE, 0 for not UTF-16 + return 1 + is_bigendian; +} + +size_t utf16_to_utf8(const uint16_t *u16_str, size_t u16_str_len, uint8_t *u8_str, size_t u8_str_size, bool big_endian) // Code from Stack Overflow +{ + size_t i = 0, j = 0; + + if (!u8_str) + u8_str_size = u16_str_len * 4; + + while (i < u16_str_len) + { + uint32_t codepoint = u16_str[i++]; + + // check for surrogate pair + if (codepoint >= 0xD800 && codepoint <= 0xDBFF) + { + uint16_t high_surr = codepoint; + uint16_t low_surr = u16_str[i++]; + + if (low_surr < 0xDC00 || low_surr > 0xDFFF) + return -1; + + codepoint = ((high_surr - 0xD800) << 10) + + (low_surr - 0xDC00) + 0x10000; + } + + if (codepoint < 0x80) + { + if (j + 1 > u8_str_size) + return -2; + + if (u8_str) + u8_str[j] = (char)codepoint; + + j++; + } + else if (codepoint < 0x800) + { + if (j + 2 > u8_str_size) + return -2; + + if (u8_str) + { + u8_str[j + 0] = 0xC0 | (codepoint >> 6); + u8_str[j + 1] = 0x80 | (codepoint & 0x3F); + } + + j += 2; + } + else if (codepoint < 0x10000) + { + if (j + 3 > u8_str_size) + return -2; + + if (u8_str) + { + u8_str[j + 0] = 0xE0 | (codepoint >> 12); + u8_str[j + 1] = 0x80 | ((codepoint >> 6) & 0x3F); + u8_str[j + 2] = 0x80 | (codepoint & 0x3F); + } + + j += 3; + } + else + { + if (j + 4 > u8_str_size) + return -2; + + if (u8_str) + { + u8_str[j + 0] = 0xF0 | (codepoint >> 18); + u8_str[j + 1] = 0x80 | ((codepoint >> 12) & 0x3F); + u8_str[j + 2] = 0x80 | ((codepoint >> 6) & 0x3F); + u8_str[j + 3] = 0x80 | (codepoint & 0x3F); + } + + j += 4; + } + } + + if (u8_str) + { + if (j >= u8_str_size) + return -2; + u8_str[j] = '\0'; + } + + return (long int)j; +} \ No newline at end of file diff --git a/src/unicode.h b/src/unicode.h new file mode 100644 index 0000000..db08bd7 --- /dev/null +++ b/src/unicode.h @@ -0,0 +1,33 @@ +#ifndef UNICODE_H +#define UNICODE_H +#include "utf8/utf8.h" +#include "newtrodit_core.h" +#include "line.h" + +#ifndef CP_UTF8 +#define CP_UTF8 65001 +#endif + +int get_file_encoding(char *header, size_t header_size, size_t *p_bom_len); +size_t codepoint_len(int32_t value); +int valid_position(char *s, size_t pos); +size_t utf8wnlen(char *s, size_t max_upos); +size_t utf8wlen(char *s); +size_t correct_position(Line *line, size_t *xpos, size_t *uxpos, size_t *uwxpos); +char *int32_to_char_array(utf8_int32_t value, char *output); +int codepoint_width(const char *utf8_char, utf8_int32_t val); +size_t next_char_uwlen(const char *s, size_t xps); +size_t previous_char_uwlen(const char *s, size_t xps); +size_t next_char_xpos(const char *s, size_t *xpos); +size_t previous_char_xpos(const char *s, size_t *xpos); +size_t utf8getxpos(char *s, size_t uxpos); +char *insert_utf8_char(Line *line, int32_t value, size_t index); +char *insert_str(Line *line, char *s2, size_t pos); +char *delete_str(Line *line, size_t pos, size_t count); +char *delete_char(Line *line, size_t pos); +int display_char(utf8_int32_t chr, FILE *fp); +uint16_t swap_le_be(uint16_t num); +int is_utf16(const unsigned char *header, size_t header_size, size_t file_size); +size_t utf16_to_utf8(const uint16_t *u16_str, size_t u16_str_len, uint8_t *u8_str, size_t u8_str_size, bool big_endian); + +#endif // UNICODE_H \ No newline at end of file diff --git a/src/utf8/LICENSE b/src/utf8/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/src/utf8/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/src/utf8/README.md b/src/utf8/README.md new file mode 100644 index 0000000..8d93ce2 --- /dev/null +++ b/src/utf8/README.md @@ -0,0 +1,389 @@ +# 📚 utf8.. + + +[![Actions Status](https://github.com/sheredom/utf8.h/workflows/CMake/badge.svg)](https://github.com/sheredom/utf8.h/actions) +[![Build status](https://ci.appveyor.com/api/projects/status/phfjjahhs9j4gxvs?svg=true)](https://ci.appveyor.com/project/sheredom/utf8-h) +[![Sponsor](https://img.shields.io/badge/💜-sponsor-blueviolet)](https://github.com/sponsors/sheredom. + + +A simple one header solution to supporting utf8 strings in C and C++. + + +Functions provided from the C header string.h but with a utf8* prefix instead of the str* prefix. + + +[API function docs](#api-function-docs. + + +string.h | utf8.h | complete | C++14 constexpr +---------|--------|---------|--------- +strcat | utf8cat | ✔ | +strchr | utf8chr | ✔ | ✔ +strcmp | utf8cmp | ✔ | ✔ +strcoll | utf8coll | | +strcpy | utf8cpy | ✔ | +strcspn | utf8cspn | ✔ | ✔ +strdup | utf8dup | ✔ | +strfry | utf8fry | | +strlen | utf8len | ✔ | ✔ +strnlen | utf8nlen | ✔ | ✔ +strncat | utf8ncat | ✔ | +strncmp | utf8ncmp | ✔ | ✔ +strncpy | utf8ncpy | ✔ | +strndup | utf8ndup | ✔ | +strpbrk | utf8pbrk | ✔ | ✔ +strrchr | utf8rchr | ✔ | ✔ +strsep | utf8sep | | +strspn | utf8spn | ✔ | ✔ +strstr | utf8str | ✔ | ✔ +strtok | utf8tok | | +strxfrm | utf8xfrm | . + + +Functions provided from the C header strings.h but with a utf8* prefix instead of the str* prefix. + + +strings.h | utf8.h | complete | C++14 constexpr +----------|--------|---------|--------- +strcasecmp | utf8casecmp | ~~✔~~ | ✔ +strncasecmp | utf8ncasecmp | ~~✔~~ | ✔ +strcasestr | utf8casestr | ~~✔~~ | ✔. + + +Functions provided that are unique to utf8.h. + + +utf8.h | complete | C++14 constexpr +-------|---------|--------- +utf8codepoint | ✔ | ✔ +utf8rcodepoint | ✔ | ✔ +utf8size | ✔ | ✔ +utf8size\_lazy | ✔ | ✔ +utf8nsize\_lazy | ✔ | ✔ +utf8valid | ✔ | ✔ +utf8nvalid | ✔ | ✔ +utf8makevalid | ✔ | +utf8codepointsize | ✔ | ✔ +utf8catcodepoint | ✔ | +utf8isupper | ~~✔~~ | ✔ +utf8islower | ~~✔~~ | ✔ +utf8lwr | ~~✔~~ | +utf8upr | ~~✔~~ | +utf8lwrcodepoint | ~~✔~~ | ✔ +utf8uprcodepoint | ~~✔~~ | ✔. + + +## Usage #. + + +Just `#include "utf8.h"` in your code. + + +The current supported platforms are Linux, macOS and Windows. + + +The current supported compilers are gcc, clang, MSVC's cl.exe, and clang-cl.exe. + + +## Design #. + + +The utf8.h API matches the string.h API as much as possible by design. There are a few major differences though. + + +utf8.h uses char8_t* in C++ 20 instead of char. + + +Anywhere in the string.h or strings.h documentation where it refers to 'bytes' I have changed that to utf8 codepoints. For instance, utf8len will return the number of utf8 codepoints in a utf8 string - which does not necessarily equate to the number of bytes. + + +## API function docs #. + + +```c +int utf8casecmp(const void *src1, const void *src2); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, `src1 == src2`, +`src1 > src2` respectively, case insensitive. + + +```c +void *utf8cat(void *dst, const void *src); +``` +Append the utf8 string `src` onto the utf8 string `dst`. + + +```c +void *utf8chr(const void *src, utf8_int32_t chr); +``` +Find the first match of the utf8 codepoint `chr` in the utf8 string `src`. + + +```c +int utf8cmp(const void *src1, const void *src2); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, +`src1 == src2`, `src1 > src2` respectively. + + +```c +void *utf8cpy(void *dst, const void *src); +``` +Copy the utf8 string `src` onto the memory allocated in `dst`. + + +```c +size_t utf8cspn(const void *src, const void *reject); +``` +Number of utf8 codepoints in the utf8 string `src` that consists entirely +of utf8 codepoints not from the utf8 string `reject`. + + +```c +void *utf8dup(const void *src); +``` +Duplicate the utf8 string `src` by getting its size, `malloc`ing a new buffer +copying over the data, and returning that. Or 0 if `malloc` failed. + + +```c +size_t utf8len(const void *str); +``` +Number of utf8 codepoints in the utf8 string `str`, +**excluding** the null terminating byte. + + +```c +size_t utf8nlen(const void *str, size_t n); +``` +Similar to `utf8len`, except that only at most `n` bytes of `src` are looked. + + +```c +int utf8ncasecmp(const void *src1, const void *src2, size_t n); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, `src1 == src2`, +`src1 > src2` respectively, case insensitive. Checking at most `n` +bytes of each utf8 string. + + +```c +void *utf8ncat(void *dst, const void *src, size_t n); +``` +Append the utf8 string `src` onto the utf8 string `dst`, +writing at most `n+1` bytes. Can produce an invalid utf8 +string if `n` falls partway through a utf8 codepoint. + + +```c +int utf8ncmp(const void *src1, const void *src2, size_t n); +``` +Return less than 0, 0, greater than 0 if `src1 < src2`, +`src1 == src2`, `src1 > src2` respectively. Checking at most `n` +bytes of each utf8 string. + + +```c +void *utf8ncpy(void *dst, const void *src, size_t n); +``` +Copy the utf8 string `src` onto the memory allocated in `dst`. +Copies at most `n` bytes. If `n` falls partway through a utf8 +codepoint, or if `dst` doesn't have enough room for a null +terminator, the final string will be cut short to preserve +utf8 validity. + + +```c +void *utf8pbrk(const void *str, const void *accept); +``` +Locates the first occurrence in the utf8 string `str` of any byte in the +utf8 string `accept`, or 0 if no match was found. + + +```c +void *utf8rchr(const void *src, utf8_int32_t chr); +``` +Find the last match of the utf8 codepoint `chr` in the utf8 string `src`. + + +```c +size_t utf8size(const void *str); +``` +Number of bytes in the utf8 string `str`, +including the null terminating byte. + + +```c +size_t utf8size_lazy(const void *str); +``` +Similar to `utf8size`, except that the null terminating byte is **excluded**. + + +```c +size_t utf8nsize_lazy(const void *str, size_t n); +``` +Similar to `utf8size`, except that only at most `n` bytes of `src` are looked and +the null terminating byte is **excluded**. + + +```c +size_t utf8spn(const void *src, const void *accept); +``` +Number of utf8 codepoints in the utf8 string `src` that consists entirely +of utf8 codepoints from the utf8 string `accept`. + + +```c +void *utf8str(const void *haystack, const void *needle); +``` +The position of the utf8 string `needle` in the utf8 string `haystack`. + + +```c +void *utf8casestr(const void *haystack, const void *needle); +``` +The position of the utf8 string `needle` in the utf8 string `haystack`, +case insensitive. + + +```c +void *utf8valid(const void *str); +``` +Return 0 on success, or the position of the invalid utf8 codepoint on failure. + + +```c +void *utf8nvalid(const void *str, size_t n); +``` +Similar to `utf8valid`, except that only at most `n` bytes of `src` are looked. + + +```c +int utf8makevalid(void *str, utf8_int32_t replacement); +``` +Return 0 on success. Makes the `str` valid by replacing invalid sequences with +the 1-byte `replacement` codepoint. + + +```c +void *utf8codepoint(const void *str, utf8_int32_t *out_codepoint); +``` +Sets out_codepoint to the current utf8 codepoint in `str`, and returns the +address of the next utf8 codepoint after the current one in `str`. + + +```c +void *utf8rcodepoint(const void *str, utf8_int32_t *out_codepoint); +``` +Sets out_codepoint to the current utf8 codepoint in `str`, and returns the +address of the previous utf8 codepoint before the current one in `str`. + + +```c +size_t utf8codepointsize(utf8_int32_t chr); +``` +Returns the size of the given codepoint in bytes. + + +```c +void *utf8catcodepoint(void *utf8_restrict str, utf8_int32_t chr, size_t n); +``` +Write a codepoint to the given string, and return the address to the next +place after the written codepoint. Pass how many bytes left in the buffer to +n. If there is not enough space for the codepoint, this function returns +null. + + +```c +int utf8islower(utf8_int32_t chr); +``` +Returns 1 if the given character is lowercase, or 0 if it is not. + + +```c +int utf8isupper(utf8_int32_t chr); +``` +Returns 1 if the given character is uppercase, or 0 if it is not. + + +```c +void utf8lwr(void *utf8_restrict str); +``` +Transform the given string into all lowercase codepoints. + + +```c +void utf8upr(void *utf8_restrict str); +``` +Transform the given string into all uppercase codepoints. + + +```c +utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); +``` +Make a codepoint lower case if possible. + + +```c +utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); +``` +Make a codepoint upper case if possible. + + +## Codepoint Cas. + + +Various functions provided will do case insensitive compares, or transform utf8 +strings from one case to another. Given the vastness of unicode, and the authors +lack of understanding beyond latin codepoints on whether case means anything, +the following categories are the only ones that will be checked in case +insensitive code. + + +* [ASCII](https://en.wikipedia.org/wiki/Basic_Latin_(Unicode_block)) +* [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)) +* [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) +* [Latin Extended-B](https://en.wikipedia.org/wiki/Latin_Extended-B) +* [Greek and Coptic](https://en.wikipedia.org/wiki/Greek_and_Coptic) +* [Cyrillic](https://en.wikipedia.org/wiki/Cyrillic_(Unicode_block). + + +## Todo #. + + +- Implement utf8coll (akin to strcoll). +- Implement utf8fry (akin to strfry). +- Investigate adding dst buffer sizes for utf8cpy and utf8cat to catch overwrites (as suggested by [@FlohOfWoe](https://twitter.com/FlohOfWoe) in https://twitter.com/FlohOfWoe/status/618669237771608064. + + +## License #. + + +This is free and unencumbered software released into the public domain. + + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + +For more information, please refer to diff --git a/src/utf8/appveyor.yml b/src/utf8/appveyor.yml new file mode 100644 index 0000000..cba55a0 --- /dev/null +++ b/src/utf8/appveyor.yml @@ -0,0 +1,51 @@ +version: '{build}' + +skip_tags: true +skip_branch_with_pr: true + +install: [] + +environment: + matrix: + - VSVERSION: Visual Studio 9 2008 + - VSVERSION: Visual Studio 10 2010 + - VSVERSION: Visual Studio 11 2012 + - VSVERSION: Visual Studio 12 2013 + - VSVERSION: Visual Studio 14 2015 + - VSVERSION: Visual Studio 15 2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - VSVERSION: Visual Studio 16 2019 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + +platform: + - Win32 + - x64 + +matrix: + exclude: + - platform: x64 + VSVERSION: Visual Studio 9 2008 + # VS 2019 / 64-bit is tested in GitHub Actions instead. + - platform: x64 + VSVERSION: Visual Studio 16 2019 + +configuration: + - Debug + # Removed to reduce configuration explosion. + # - RelWithDebInfo + # - MinSizeRel + - Release + +build_script: + - md build + - cd build + - if NOT "%VSVERSION%"=="Visual Studio 16 2019" if "%PLATFORM%"=="x64" cmake -G "%VSVERSION% Win64" ../test + - if NOT "%VSVERSION%"=="Visual Studio 16 2019" if "%PLATFORM%"=="Win32" cmake -G "%VSVERSION%" ../test + - if "%VSVERSION%"=="Visual Studio 16 2019" cmake -G "%VSVERSION%" -A "%PLATFORM%" ../test + - msbuild /m /p:Configuration="%CONFIGURATION%" /p:Platform="%PLATFORM%" utf8.sln + - copy %CONFIGURATION%\utf8_test.exe utf8_test.exe + - copy %CONFIGURATION%\utf8_no_malloc_test.exe utf8_no_malloc_test.exe + +test_script: + - utf8_test.exe + - utf8_no_malloc_test.exe diff --git a/src/utf8/test/CMakeLists.txt b/src/utf8/test/CMakeLists.txt new file mode 100644 index 0000000..a89f926 --- /dev/null +++ b/src/utf8/test/CMakeLists.txt @@ -0,0 +1,117 @@ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +project(utf8) +cmake_minimum_required(VERSION 2.8.12) + +set(UTF8_USE_SANITIZER "" CACHE STRING "Set which Clang Sanitizer to use") + +macro(add_sanitizer target) + if(NOT "${UTF8_USE_SANITIZER}" STREQUAL "") + target_compile_options(${target} PUBLIC -fno-omit-frame-pointer -fsanitize=${UTF8_USE_SANITIZER}) + target_link_options(${target} PUBLIC -fno-omit-frame-pointer -fsanitize=${UTF8_USE_SANITIZER}) + endif() +endmacro() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_executable(utf8_test main.c) +add_sanitizer(utf8_test) + +add_executable(utf8_no_malloc_test no_malloc.c) +add_sanitizer(utf8_no_malloc_test) + +add_executable(utf8_test_c90 test.c) +add_sanitizer(utf8_test_c90) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c90 PUBLIC "-std=c90") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c90 PUBLIC "-std=c90") + endif() +endif() + +add_executable(utf8_test_c99 test.c) +add_sanitizer(utf8_test_c99) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c99 PUBLIC "-std=c99") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c99 PUBLIC "-std=c99") + endif() +endif() + +add_executable(utf8_test_c11 test.c) +add_sanitizer(utf8_test_c11) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + target_compile_options(utf8_test_c11 PUBLIC "-std=c11") +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + else() + target_compile_options(utf8_test_c11 PUBLIC "-std=c11") + endif() +endif() + +add_executable(utf8_test_cpp11 test.cpp) +add_sanitizer(utf8_test_cpp11) +set_target_properties(utf8_test_cpp11 PROPERTIES CXX_STANDARD 11) + +add_executable(utf8_test_cpp14 test.cpp) +add_sanitizer(utf8_test_cpp14) +set_target_properties(utf8_test_cpp14 PROPERTIES CXX_STANDARD 14) + +add_executable(utf8_test_cpp17 test.cpp) +add_sanitizer(utf8_test_cpp17) +set_target_properties(utf8_test_cpp17 PROPERTIES CXX_STANDARD 17) + +add_executable(utf8_test_cpp20 test.cpp) +add_sanitizer(utf8_test_cpp20) +set_target_properties(utf8_test_cpp20 PROPERTIES CXX_STANDARD 20) + +if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Werror" + ) +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -Wno-c++98-compat" + ) +elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514" + ) + if(${MSVC_VERSION} VERSION_GREATER "15.7") + set_source_files_properties(test.c test.cpp PROPERTIES + COMPILE_FLAGS "/Zc:__cplusplus" + ) + endif() +else() + message(WARNING "Unknown compiler '${CMAKE_C_COMPILER_ID}'!") +endif() diff --git a/src/utf8/test/main.c b/src/utf8/test/main.c new file mode 100644 index 0000000..3739fbe --- /dev/null +++ b/src/utf8/test/main.c @@ -0,0 +1,1669 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +// include the unit testing framework +#include "utest.h" + +// include the header we are testing +#include "utf8.h" + +const char data[] = { + '\xce', '\x93', '\xce', '\xb1', '\xce', '\xb6', '\xce', '\xad', '\xce', + '\xb5', '\xcf', '\x82', '\x20', '\xce', '\xba', '\xce', '\xb1', '\xe1', + '\xbd', '\xb6', '\x20', '\xce', '\xbc', '\xcf', '\x85', '\xcf', '\x81', + '\xcf', '\x84', '\xce', '\xb9', '\xe1', '\xbd', '\xb2', '\xcf', '\x82', + '\x20', '\xce', '\xb4', '\xe1', '\xbd', '\xb2', '\xce', '\xbd', '\x20', + '\xce', '\xb8', '\xe1', '\xbd', '\xb0', '\x20', '\xce', '\xb2', '\xcf', + '\x81', '\xe1', '\xbf', '\xb6', '\x20', '\xcf', '\x80', '\xce', '\xb9', + '\xe1', '\xbd', '\xb0', '\x20', '\xcf', '\x83', '\xcf', '\x84', '\xe1', + '\xbd', '\xb8', '\x20', '\xcf', '\x87', '\xcf', '\x81', '\xcf', '\x85', + '\xcf', '\x83', '\xce', '\xb1', '\xcf', '\x86', '\xe1', '\xbd', '\xb6', + '\x20', '\xce', '\xbe', '\xce', '\xad', '\xcf', '\x86', '\xcf', '\x89', + '\xcf', '\x84', '\xce', '\xbf', '\x0a', '\0'}; + +const char cmp[] = {'\xce', '\xbc', '\xcf', '\x85', '\0'}; + +const char lt[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', + '\xb6', '\xce', '\xac', '\0'}; + +const char gt[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', + '\xb6', '\xce', '\xae', '\0'}; + +const char spn[] = {'\xce', '\x93', '\xce', '\xb1', '\xce', '\xb6', + '\xce', '\xad', '\xce', '\xb5', '\xcf', '\x82', + '\x20', '\xce', '\xba', '\0'}; + +const char pbrk[] = {'\xcf', '\x82', '\x20', '\xce', '\xb5', '\0'}; + +const char ascii1[] = "I lIke GOATS YARHAR."; +const char ascii2[] = "i LIKE goats yarHAR."; +const char allascii1[] = "abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWYZ"; +const char allascii2[] = "ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwyz"; +const char haystack[] = "foobar"; +const char needle[] = "oba"; +const char endfailneedle[] = "ra"; +const char cspnmultisearch[] = "another test; string|one more"; +const char cspnmultidelims[] = "|;"; +const char spnasciisearch[] = ",,hello,,world"; +const char spnasciidelims[] = ","; + +struct LowerUpperPair { + int lower; + int upper; +}; + +const struct LowerUpperPair lowupPairs[] = { + /* ascii */ + {0x0061, 0x0041}, + {0x0062, 0x0042}, + {0x0063, 0x0043}, + {0x0064, 0x0044}, + {0x0065, 0x0045}, + {0x0066, 0x0046}, + {0x0067, 0x0047}, + {0x0068, 0x0048}, + {0x0069, 0x0049}, + {0x006a, 0x004a}, + {0x006b, 0x004b}, + {0x006c, 0x004c}, + {0x006d, 0x004d}, + {0x006e, 0x004e}, + {0x006f, 0x004f}, + {0x0070, 0x0050}, + {0x0071, 0x0051}, + {0x0072, 0x0052}, + {0x0073, 0x0053}, + {0x0074, 0x0054}, + {0x0075, 0x0055}, + {0x0076, 0x0056}, + {0x0077, 0x0057}, + {0x0078, 0x0058}, + {0x0079, 0x0059}, + {0x007a, 0x005a}, + + /* Latin-1 Supplement */ + {0x00e0, 0x00c0}, + {0x00e1, 0x00c1}, + {0x00e2, 0x00c2}, + {0x00e3, 0x00c3}, + {0x00e4, 0x00c4}, + {0x00e5, 0x00c5}, + {0x00e6, 0x00c6}, + {0x00e7, 0x00c7}, + {0x00e8, 0x00c8}, + {0x00e9, 0x00c9}, + {0x00ea, 0x00ca}, + {0x00eb, 0x00cb}, + {0x00ec, 0x00cc}, + {0x00ed, 0x00cd}, + {0x00ee, 0x00ce}, + {0x00ef, 0x00cf}, + {0x00f0, 0x00d0}, + {0x00f1, 0x00d1}, + {0x00f2, 0x00d2}, + {0x00f3, 0x00d3}, + {0x00f4, 0x00d4}, + {0x00f5, 0x00d5}, + {0x00f6, 0x00d6}, + {0x00f8, 0x00d8}, + {0x00f9, 0x00d9}, + {0x00fa, 0x00da}, + {0x00fb, 0x00db}, + {0x00fc, 0x00dc}, + {0x00fd, 0x00dd}, + {0x00fe, 0x00de}, + {0x00ff, 0x0178}, + + /* Latin Extended-A */ + {0x0101, 0x0100}, + {0x0103, 0x0102}, + {0x0105, 0x0104}, + {0x0107, 0x0106}, + {0x0109, 0x0108}, + {0x010b, 0x010a}, + {0x010d, 0x010c}, + {0x010f, 0x010e}, + {0x0111, 0x0110}, + {0x0113, 0x0112}, + {0x0115, 0x0114}, + {0x0117, 0x0116}, + {0x0119, 0x0118}, + {0x011b, 0x011a}, + {0x011d, 0x011c}, + {0x011f, 0x011e}, + {0x0121, 0x0120}, + {0x0123, 0x0122}, + {0x0125, 0x0124}, + {0x0127, 0x0126}, + {0x0129, 0x0128}, + {0x012b, 0x012a}, + {0x012d, 0x012c}, + {0x012f, 0x012e}, + {0x0133, 0x0132}, + {0x0135, 0x0134}, + {0x0137, 0x0136}, + {0x013a, 0x0139}, + {0x013c, 0x013b}, + {0x013e, 0x013d}, + {0x0140, 0x013f}, + {0x0142, 0x0141}, + {0x0144, 0x0143}, + {0x0146, 0x0145}, + {0x0148, 0x0147}, + {0x014b, 0x014a}, + {0x014d, 0x014c}, + {0x014f, 0x014e}, + {0x0151, 0x0150}, + {0x0153, 0x0152}, + {0x0155, 0x0154}, + {0x0157, 0x0156}, + {0x0159, 0x0158}, + {0x015b, 0x015a}, + {0x015d, 0x015c}, + {0x015f, 0x015e}, + {0x0161, 0x0160}, + {0x0163, 0x0162}, + {0x0165, 0x0164}, + {0x0167, 0x0166}, + {0x0169, 0x0168}, + {0x016b, 0x016a}, + {0x016d, 0x016c}, + {0x016f, 0x016e}, + {0x0171, 0x0170}, + {0x0173, 0x0172}, + {0x0175, 0x0174}, + {0x0177, 0x0176}, + {0x017a, 0x0179}, + {0x017c, 0x017b}, + {0x017e, 0x017d}, + + /* Latin Extended-B */ + {0x0180, 0x0243}, + {0x01dd, 0x018e}, + {0x019a, 0x023d}, + {0x019e, 0x0220}, + {0x0292, 0x01b7}, + {0x01c6, 0x01c4}, + {0x01c9, 0x01c7}, + {0x01cc, 0x01ca}, + {0x01f3, 0x01f1}, + {0x01bf, 0x01f7}, + {0x0183, 0x0182}, + {0x0185, 0x0184}, + {0x0188, 0x0187}, + {0x018c, 0x018b}, + {0x0192, 0x0191}, + {0x0199, 0x0198}, + {0x01a1, 0x01a0}, + {0x01a3, 0x01a2}, + {0x01a5, 0x01a4}, + {0x01a8, 0x01a7}, + {0x01ad, 0x01ac}, + {0x01b0, 0x01af}, + {0x01b4, 0x01b3}, + {0x01b6, 0x01b5}, + {0x01b9, 0x01b8}, + {0x01bd, 0x01bc}, + {0x01ce, 0x01cd}, + {0x01d0, 0x01cf}, + {0x01d2, 0x01d1}, + {0x01d4, 0x01d3}, + {0x01d6, 0x01d5}, + {0x01d8, 0x01d7}, + {0x01da, 0x01d9}, + {0x01dc, 0x01db}, + {0x01df, 0x01de}, + {0x01e1, 0x01e0}, + {0x01e3, 0x01e2}, + {0x01e5, 0x01e4}, + {0x01e7, 0x01e6}, + {0x01e9, 0x01e8}, + {0x01eb, 0x01ea}, + {0x01ed, 0x01ec}, + {0x01ef, 0x01ee}, + {0x01f5, 0x01f4}, + {0x01f9, 0x01f8}, + {0x01fb, 0x01fa}, + {0x01fd, 0x01fc}, + {0x01ff, 0x01fe}, + {0x0201, 0x0200}, + {0x0203, 0x0202}, + {0x0205, 0x0204}, + {0x0207, 0x0206}, + {0x0209, 0x0208}, + {0x020b, 0x020a}, + {0x020d, 0x020c}, + {0x020f, 0x020e}, + {0x0211, 0x0210}, + {0x0213, 0x0212}, + {0x0215, 0x0214}, + {0x0217, 0x0216}, + {0x0219, 0x0218}, + {0x021b, 0x021a}, + {0x021d, 0x021c}, + {0x021f, 0x021e}, + {0x0223, 0x0222}, + {0x0225, 0x0224}, + {0x0227, 0x0226}, + {0x0229, 0x0228}, + {0x022b, 0x022a}, + {0x022d, 0x022c}, + {0x022f, 0x022e}, + {0x0231, 0x0230}, + {0x0233, 0x0232}, + {0x023c, 0x023b}, + {0x0242, 0x0241}, + {0x0247, 0x0246}, + {0x0249, 0x0248}, + {0x024b, 0x024a}, + {0x024d, 0x024c}, + {0x024f, 0x024e}, + + /* Greek and Coptic */ + {0x037b, 0x03fd}, + {0x037c, 0x03fe}, + {0x037d, 0x03ff}, + + {0x03f3, 0x037f}, + {0x03ac, 0x0386}, + + {0x03ad, 0x0388}, + {0x03ae, 0x0389}, + {0x03af, 0x038a}, + + {0x03cc, 0x038c}, + + {0x03cd, 0x038e}, + {0x03ce, 0x038f}, + + {0x0371, 0x0370}, + {0x0373, 0x0372}, + {0x0377, 0x0376}, + + {0x03B1, 0x0391}, + {0x03B2, 0x0392}, + {0x03B3, 0x0393}, + {0x03B4, 0x0394}, + {0x03B5, 0x0395}, + {0x03B6, 0x0396}, + {0x03B7, 0x0397}, + {0x03B8, 0x0398}, + {0x03B9, 0x0399}, + {0x03BA, 0x039A}, + {0x03BB, 0x039B}, + {0x03BC, 0x039C}, + {0x03BD, 0x039D}, + {0x03BE, 0x039E}, + {0x03BF, 0x039F}, + {0x03C0, 0x03A0}, + {0x03C1, 0x03A1}, + + {0x03C3, 0x03A3}, + {0x03C4, 0x03A4}, + {0x03C5, 0x03A5}, + {0x03C6, 0x03A6}, + {0x03C7, 0x03A7}, + {0x03C8, 0x03A8}, + {0x03C9, 0x03A9}, + {0x03ca, 0x03aa}, + {0x03cb, 0x03ab}, + + {0x03d1, 0x03f4}, + + {0x03d7, 0x03cf}, + + {0x03d9, 0x03d8}, + {0x03db, 0x03da}, + {0x03dd, 0x03dc}, + {0x03df, 0x03de}, + {0x03e1, 0x03e0}, + {0x03e3, 0x03e2}, + {0x03e5, 0x03e4}, + {0x03e7, 0x03e6}, + {0x03e9, 0x03e8}, + {0x03eb, 0x03ea}, + {0x03ed, 0x03ec}, + {0x03ef, 0x03ee}, + + {0x03f2, 0x03f9}, + + {0x03f8, 0x03f7}, + + {0x03fb, 0x03fa}, + + /* Cyrillic */ + {0x0450, 0x0400}, + {0x0451, 0x0401}, + {0x0452, 0x0402}, + {0x0453, 0x0403}, + {0x0454, 0x0404}, + {0x0455, 0x0405}, + {0x0456, 0x0406}, + {0x0457, 0x0407}, + {0x0458, 0x0408}, + {0x0459, 0x0409}, + {0x045a, 0x040a}, + {0x045b, 0x040b}, + {0x045c, 0x040c}, + {0x045d, 0x040d}, + {0x045e, 0x040e}, + {0x045f, 0x040f}, + + {0x0430, 0x0410}, + {0x0431, 0x0411}, + {0x0432, 0x0412}, + {0x0433, 0x0413}, + {0x0434, 0x0414}, + {0x0435, 0x0415}, + {0x0436, 0x0416}, + {0x0437, 0x0417}, + {0x0438, 0x0418}, + {0x0439, 0x0419}, + {0x043a, 0x041a}, + {0x043b, 0x041b}, + {0x043c, 0x041c}, + {0x043d, 0x041d}, + {0x043e, 0x041e}, + {0x043f, 0x041f}, + {0x0440, 0x0420}, + {0x0441, 0x0421}, + {0x0442, 0x0422}, + {0x0443, 0x0423}, + {0x0444, 0x0424}, + {0x0445, 0x0425}, + {0x0446, 0x0426}, + {0x0447, 0x0427}, + {0x0448, 0x0428}, + {0x0449, 0x0429}, + {0x044a, 0x042a}, + {0x044b, 0x042b}, + {0x044c, 0x042c}, + {0x044d, 0x042d}, + {0x044e, 0x042e}, + {0x044f, 0x042f}, + + {0x0461, 0x0460}, + {0x0463, 0x0462}, + {0x0465, 0x0464}, + {0x0467, 0x0466}, + {0x0469, 0x0468}, + {0x046b, 0x046a}, + {0x046d, 0x046c}, + {0x046f, 0x046e}, + {0x0471, 0x0470}, + {0x0473, 0x0472}, + {0x0475, 0x0474}, + {0x0477, 0x0476}, + {0x0479, 0x0478}, + {0x047b, 0x047a}, + {0x047d, 0x047c}, + {0x047f, 0x047e}, + {0x0481, 0x0480}, + + {0x048b, 0x048a}, + {0x048d, 0x048c}, + {0x048f, 0x048e}, + {0x0491, 0x0490}, + {0x0493, 0x0492}, + {0x0495, 0x0494}, + {0x0497, 0x0496}, + {0x0499, 0x0498}, + {0x049b, 0x049a}, + {0x049d, 0x049c}, + {0x049f, 0x049e}, + {0x04a1, 0x04a0}, + {0x04a3, 0x04a2}, + {0x04a5, 0x04a4}, + {0x04a7, 0x04a6}, + {0x04a9, 0x04a8}, + {0x04ab, 0x04aa}, + {0x04ad, 0x04ac}, + {0x04af, 0x04ae}, + {0x04b1, 0x04b0}, + {0x04b3, 0x04b2}, + {0x04b5, 0x04b4}, + {0x04b7, 0x04b6}, + {0x04b9, 0x04b8}, + {0x04bb, 0x04ba}, + {0x04bd, 0x04bc}, + {0x04bf, 0x04be}, + {0x04c1, 0x04c0}, + {0x04c3, 0x04c2}, + {0x04c5, 0x04c4}, + {0x04c7, 0x04c6}, + {0x04c9, 0x04c8}, + {0x04cb, 0x04ca}, + {0x04cd, 0x04cc}, + {0x04cf, 0x04ce}, + {0x04d1, 0x04d0}, + {0x04d3, 0x04d2}, + {0x04d5, 0x04d4}, + {0x04d7, 0x04d6}, + {0x04d9, 0x04d8}, + {0x04db, 0x04da}, + {0x04dd, 0x04dc}, + {0x04df, 0x04de}, + {0x04e1, 0x04e0}, + {0x04e3, 0x04e2}, + {0x04e5, 0x04e4}, + {0x04e7, 0x04e6}, + {0x04e9, 0x04e8}, + {0x04eb, 0x04ea}, + {0x04ed, 0x04ec}, + {0x04ef, 0x04ee}, + {0x04f1, 0x04f0}, + {0x04f3, 0x04f2}, + {0x04f5, 0x04f4}, + {0x04f7, 0x04f6}, + {0x04f9, 0x04f8}, + {0x04fb, 0x04fa}, + {0x04fd, 0x04fc}, + {0x04ff, 0x04fe}, + + // End of array marker + {0, 0}}; + +const char lowersStr[] = { + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', + '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', '\x70', '\x71', '\x72', + '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7a', '\xc3', + '\xa0', '\xc3', '\xa1', '\xc3', '\xa2', '\xc3', '\xa3', '\xc3', '\xa4', + '\xc3', '\xa5', '\xc3', '\xa6', '\xc3', '\xa7', '\xc3', '\xa8', '\xc3', + '\xa9', '\xc3', '\xaa', '\xc3', '\xab', '\xc3', '\xac', '\xc3', '\xad', + '\xc3', '\xae', '\xc3', '\xaf', '\xc3', '\xb0', '\xc3', '\xb1', '\xc3', + '\xb2', '\xc3', '\xb3', '\xc3', '\xb4', '\xc3', '\xb5', '\xc3', '\xb6', + '\xc3', '\xb8', '\xc3', '\xb9', '\xc3', '\xba', '\xc3', '\xbb', '\xc3', + '\xbc', '\xc3', '\xbd', '\xc3', '\xbe', '\xc3', '\xbf', '\xc4', '\x81', + '\xc4', '\x83', '\xc4', '\x85', '\xc4', '\x87', '\xc4', '\x89', '\xc4', + '\x8b', '\xc4', '\x8d', '\xc4', '\x8f', '\xc4', '\x91', '\xc4', '\x93', + '\xc4', '\x95', '\xc4', '\x97', '\xc4', '\x99', '\xc4', '\x9b', '\xc4', + '\x9d', '\xc4', '\x9f', '\xc4', '\xa1', '\xc4', '\xa3', '\xc4', '\xa5', + '\xc4', '\xa7', '\xc4', '\xa9', '\xc4', '\xab', '\xc4', '\xad', '\xc4', + '\xaf', '\xc4', '\xb3', '\xc4', '\xb5', '\xc4', '\xb7', '\xc4', '\xba', + '\xc4', '\xbc', '\xc4', '\xbe', '\xc5', '\x80', '\xc5', '\x82', '\xc5', + '\x84', '\xc5', '\x86', '\xc5', '\x88', '\xc5', '\x8b', '\xc5', '\x8d', + '\xc5', '\x8f', '\xc5', '\x91', '\xc5', '\x93', '\xc5', '\x95', '\xc5', + '\x97', '\xc5', '\x99', '\xc5', '\x9b', '\xc5', '\x9d', '\xc5', '\x9f', + '\xc5', '\xa1', '\xc5', '\xa3', '\xc5', '\xa5', '\xc5', '\xa7', '\xc5', + '\xa9', '\xc5', '\xab', '\xc5', '\xad', '\xc5', '\xaf', '\xc5', '\xb1', + '\xc5', '\xb3', '\xc5', '\xb5', '\xc5', '\xb7', '\xc5', '\xba', '\xc5', + '\xbc', '\xc5', '\xbe', '\xc6', '\x80', '\xc7', '\x9d', '\xc6', '\x9a', + '\xc6', '\x9e', '\xca', '\x92', '\xc7', '\x86', '\xc7', '\x89', '\xc7', + '\x8c', '\xc7', '\xb3', '\xc6', '\xbf', '\xc6', '\x83', '\xc6', '\x85', + '\xc6', '\x88', '\xc6', '\x8c', '\xc6', '\x92', '\xc6', '\x99', '\xc6', + '\xa1', '\xc6', '\xa3', '\xc6', '\xa5', '\xc6', '\xa8', '\xc6', '\xad', + '\xc6', '\xb0', '\xc6', '\xb4', '\xc6', '\xb6', '\xc6', '\xb9', '\xc6', + '\xbd', '\xc7', '\x8e', '\xc7', '\x90', '\xc7', '\x92', '\xc7', '\x94', + '\xc7', '\x96', '\xc7', '\x98', '\xc7', '\x9a', '\xc7', '\x9c', '\xc7', + '\x9f', '\xc7', '\xa1', '\xc7', '\xa3', '\xc7', '\xa5', '\xc7', '\xa7', + '\xc7', '\xa9', '\xc7', '\xab', '\xc7', '\xad', '\xc7', '\xaf', '\xc7', + '\xb5', '\xc7', '\xb9', '\xc7', '\xbb', '\xc7', '\xbd', '\xc7', '\xbf', + '\xc8', '\x81', '\xc8', '\x83', '\xc8', '\x85', '\xc8', '\x87', '\xc8', + '\x89', '\xc8', '\x8b', '\xc8', '\x8d', '\xc8', '\x8f', '\xc8', '\x91', + '\xc8', '\x93', '\xc8', '\x95', '\xc8', '\x97', '\xc8', '\x99', '\xc8', + '\x9b', '\xc8', '\x9d', '\xc8', '\x9f', '\xc8', '\xa3', '\xc8', '\xa5', + '\xc8', '\xa7', '\xc8', '\xa9', '\xc8', '\xab', '\xc8', '\xad', '\xc8', + '\xaf', '\xc8', '\xb1', '\xc8', '\xb3', '\xc8', '\xbc', '\xc9', '\x82', + '\xc9', '\x87', '\xc9', '\x89', '\xc9', '\x8b', '\xc9', '\x8d', '\xc9', + '\x8f', '\xcd', '\xbb', '\xcd', '\xbc', '\xcd', '\xbd', '\xcf', '\xb3', + '\xce', '\xac', '\xce', '\xad', '\xce', '\xae', '\xce', '\xaf', '\xcf', + '\x8c', '\xcf', '\x8d', '\xcf', '\x8e', '\xcd', '\xb1', '\xcd', '\xb3', + '\xcd', '\xb7', '\xce', '\xb1', '\xce', '\xb2', '\xce', '\xb3', '\xce', + '\xb4', '\xce', '\xb5', '\xce', '\xb6', '\xce', '\xb7', '\xce', '\xb8', + '\xce', '\xb9', '\xce', '\xba', '\xce', '\xbb', '\xce', '\xbc', '\xce', + '\xbd', '\xce', '\xbe', '\xce', '\xbf', '\xcf', '\x80', '\xcf', '\x81', + '\xcf', '\x83', '\xcf', '\x84', '\xcf', '\x85', '\xcf', '\x86', '\xcf', + '\x87', '\xcf', '\x88', '\xcf', '\x89', '\xcf', '\x8a', '\xcf', '\x8b', + '\xcf', '\x97', '\xcf', '\x99', '\xcf', '\x9b', '\xcf', '\x9d', '\xcf', + '\x9f', '\xcf', '\xa1', '\xcf', '\xa3', '\xcf', '\xa5', '\xcf', '\xa7', + '\xcf', '\xa9', '\xcf', '\xab', '\xcf', '\xad', '\xcf', '\xaf', '\xcf', + '\xb2', '\xcf', '\xb8', '\xcf', '\xbb', '\xd0', '\xb0', '\xd0', '\xb1', + '\xd0', '\xb2', '\xd0', '\xb3', '\xd0', '\xb4', '\xd0', '\xb5', '\xd1', + '\x91', '\xd0', '\xb6', '\xd0', '\xb7', '\xd0', '\xb8', '\xd0', '\xb9', + '\xd0', '\xba', '\xd0', '\xbb', '\xd0', '\xbc', '\xd0', '\xbd', '\xd0', + '\xbe', '\xd0', '\xbf', '\xd1', '\x80', '\xd1', '\x81', '\xd1', '\x82', + '\xd1', '\x83', '\xd1', '\x84', '\xd1', '\x85', '\xd1', '\x86', '\xd1', + '\x87', '\xd1', '\x88', '\xd1', '\x89', '\xd1', '\x8a', '\xd1', '\x8b', + '\xd1', '\x8c', '\xd1', '\x8d', '\xd1', '\x8e', '\xd1', '\x8f', '\0'}; + +const char uppersStr[] = { + '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', '\x48', '\x49', + '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', '\x50', '\x51', '\x52', + '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', '\x5a', '\xc3', + '\x80', '\xc3', '\x81', '\xc3', '\x82', '\xc3', '\x83', '\xc3', '\x84', + '\xc3', '\x85', '\xc3', '\x86', '\xc3', '\x87', '\xc3', '\x88', '\xc3', + '\x89', '\xc3', '\x8a', '\xc3', '\x8b', '\xc3', '\x8c', '\xc3', '\x8d', + '\xc3', '\x8e', '\xc3', '\x8f', '\xc3', '\x90', '\xc3', '\x91', '\xc3', + '\x92', '\xc3', '\x93', '\xc3', '\x94', '\xc3', '\x95', '\xc3', '\x96', + '\xc3', '\x98', '\xc3', '\x99', '\xc3', '\x9a', '\xc3', '\x9b', '\xc3', + '\x9c', '\xc3', '\x9d', '\xc3', '\x9e', '\xc5', '\xb8', '\xc4', '\x80', + '\xc4', '\x82', '\xc4', '\x84', '\xc4', '\x86', '\xc4', '\x88', '\xc4', + '\x8a', '\xc4', '\x8c', '\xc4', '\x8e', '\xc4', '\x90', '\xc4', '\x92', + '\xc4', '\x94', '\xc4', '\x96', '\xc4', '\x98', '\xc4', '\x9a', '\xc4', + '\x9c', '\xc4', '\x9e', '\xc4', '\xa0', '\xc4', '\xa2', '\xc4', '\xa4', + '\xc4', '\xa6', '\xc4', '\xa8', '\xc4', '\xaa', '\xc4', '\xac', '\xc4', + '\xae', '\xc4', '\xb2', '\xc4', '\xb4', '\xc4', '\xb6', '\xc4', '\xb9', + '\xc4', '\xbb', '\xc4', '\xbd', '\xc4', '\xbf', '\xc5', '\x81', '\xc5', + '\x83', '\xc5', '\x85', '\xc5', '\x87', '\xc5', '\x8a', '\xc5', '\x8c', + '\xc5', '\x8e', '\xc5', '\x90', '\xc5', '\x92', '\xc5', '\x94', '\xc5', + '\x96', '\xc5', '\x98', '\xc5', '\x9a', '\xc5', '\x9c', '\xc5', '\x9e', + '\xc5', '\xa0', '\xc5', '\xa2', '\xc5', '\xa4', '\xc5', '\xa6', '\xc5', + '\xa8', '\xc5', '\xaa', '\xc5', '\xac', '\xc5', '\xae', '\xc5', '\xb0', + '\xc5', '\xb2', '\xc5', '\xb4', '\xc5', '\xb6', '\xc5', '\xb9', '\xc5', + '\xbb', '\xc5', '\xbd', '\xc9', '\x83', '\xc6', '\x8e', '\xc8', '\xbd', + '\xc8', '\xa0', '\xc6', '\xb7', '\xc7', '\x84', '\xc7', '\x87', '\xc7', + '\x8a', '\xc7', '\xb1', '\xc7', '\xb7', '\xc6', '\x82', '\xc6', '\x84', + '\xc6', '\x87', '\xc6', '\x8b', '\xc6', '\x91', '\xc6', '\x98', '\xc6', + '\xa0', '\xc6', '\xa2', '\xc6', '\xa4', '\xc6', '\xa7', '\xc6', '\xac', + '\xc6', '\xaf', '\xc6', '\xb3', '\xc6', '\xb5', '\xc6', '\xb8', '\xc6', + '\xbc', '\xc7', '\x8d', '\xc7', '\x8f', '\xc7', '\x91', '\xc7', '\x93', + '\xc7', '\x95', '\xc7', '\x97', '\xc7', '\x99', '\xc7', '\x9b', '\xc7', + '\x9e', '\xc7', '\xa0', '\xc7', '\xa2', '\xc7', '\xa4', '\xc7', '\xa6', + '\xc7', '\xa8', '\xc7', '\xaa', '\xc7', '\xac', '\xc7', '\xae', '\xc7', + '\xb4', '\xc7', '\xb8', '\xc7', '\xba', '\xc7', '\xbc', '\xc7', '\xbe', + '\xc8', '\x80', '\xc8', '\x82', '\xc8', '\x84', '\xc8', '\x86', '\xc8', + '\x88', '\xc8', '\x8a', '\xc8', '\x8c', '\xc8', '\x8e', '\xc8', '\x90', + '\xc8', '\x92', '\xc8', '\x94', '\xc8', '\x96', '\xc8', '\x98', '\xc8', + '\x9a', '\xc8', '\x9c', '\xc8', '\x9e', '\xc8', '\xa2', '\xc8', '\xa4', + '\xc8', '\xa6', '\xc8', '\xa8', '\xc8', '\xaa', '\xc8', '\xac', '\xc8', + '\xae', '\xc8', '\xb0', '\xc8', '\xb2', '\xc8', '\xbb', '\xc9', '\x81', + '\xc9', '\x86', '\xc9', '\x88', '\xc9', '\x8a', '\xc9', '\x8c', '\xc9', + '\x8e', '\xcf', '\xbd', '\xcf', '\xbe', '\xcf', '\xbf', '\xcd', '\xbf', + '\xce', '\x86', '\xce', '\x88', '\xce', '\x89', '\xce', '\x8a', '\xce', + '\x8c', '\xce', '\x8e', '\xce', '\x8f', '\xcd', '\xb0', '\xcd', '\xb2', + '\xcd', '\xb6', '\xce', '\x91', '\xce', '\x92', '\xce', '\x93', '\xce', + '\x94', '\xce', '\x95', '\xce', '\x96', '\xce', '\x97', '\xce', '\x98', + '\xce', '\x99', '\xce', '\x9a', '\xce', '\x9b', '\xce', '\x9c', '\xce', + '\x9d', '\xce', '\x9e', '\xce', '\x9f', '\xce', '\xa0', '\xce', '\xa1', + '\xce', '\xa3', '\xce', '\xa4', '\xce', '\xa5', '\xce', '\xa6', '\xce', + '\xa7', '\xce', '\xa8', '\xce', '\xa9', '\xce', '\xaa', '\xce', '\xab', + '\xcf', '\x8f', '\xcf', '\x98', '\xcf', '\x9a', '\xcf', '\x9c', '\xcf', + '\x9e', '\xcf', '\xa0', '\xcf', '\xa2', '\xcf', '\xa4', '\xcf', '\xa6', + '\xcf', '\xa8', '\xcf', '\xaa', '\xcf', '\xac', '\xcf', '\xae', '\xcf', + '\xb9', '\xcf', '\xb7', '\xcf', '\xba', '\xd0', '\x90', '\xd0', '\x91', + '\xd0', '\x92', '\xd0', '\x93', '\xd0', '\x94', '\xd0', '\x95', '\xd0', + '\x81', '\xd0', '\x96', '\xd0', '\x97', '\xd0', '\x98', '\xd0', '\x99', + '\xd0', '\x9a', '\xd0', '\x9b', '\xd0', '\x9c', '\xd0', '\x9d', '\xd0', + '\x9e', '\xd0', '\x9f', '\xd0', '\xa0', '\xd0', '\xa1', '\xd0', '\xa2', + '\xd0', '\xa3', '\xd0', '\xa4', '\xd0', '\xa5', '\xd0', '\xa6', '\xd0', + '\xa7', '\xd0', '\xa8', '\xd0', '\xa9', '\xd0', '\xaa', '\xd0', '\xab', + '\xd0', '\xac', '\xd0', '\xad', '\xd0', '\xae', '\xd0', '\xaf', '\0'}; + +UTEST(utf8len, data) { ASSERT_EQ(53, utf8len(data)); } + +UTEST(utf8nlen, data) { ASSERT_EQ(52, utf8nlen(data, 103)); } + +UTEST(utf8cat, empty_cat_data) { + char cat[512] = {'\0'}; + + ASSERT_EQ(0, utf8len(cat)); + + ASSERT_EQ(53, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, one_byte_cat_data) { + char cat[512]; + + cat[0] = 'a'; + cat[1] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, two_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xce'; + cat[1] = '\x93'; + cat[2] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, three_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xe1'; + cat[1] = '\xbd'; + cat[2] = '\xb6'; + cat[3] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, four_bytes_cat_data) { + char cat[512]; + + cat[0] = '\xf0'; + cat[1] = '\x90'; + cat[2] = '\x8d'; + cat[3] = '\x88'; + cat[4] = '\0'; + + ASSERT_EQ(1, utf8len(cat)); + + ASSERT_EQ(54, utf8len(utf8cat(cat, data))); +} + +UTEST(utf8cat, cat_data_data) { + char cat[512] = {'\0'}; + + ASSERT_EQ(0, utf8len(cat)); + + ASSERT_EQ(106, utf8len(utf8cat(utf8cat(cat, data), data))); +} + +UTEST(utf8str, cmp) { ASSERT_EQ(data + 21, utf8str(data, cmp)); } + +UTEST(utf8str, test) { ASSERT_EQ((void *)0, utf8str(data, "test")); } + +UTEST(utf8str, empty) { ASSERT_EQ(data, utf8str(data, "")); } + +UTEST(utf8str, partial) { ASSERT_EQ(haystack + 2, utf8str(haystack, needle)); } + +UTEST(utf8str, endfail) { + ASSERT_EQ((void *)0, utf8str(haystack, endfailneedle)); +} + +UTEST(utf8casestr, cmp) { ASSERT_EQ(data + 21, utf8casestr(data, cmp)); } + +UTEST(utf8casestr, test) { ASSERT_EQ((void *)0, utf8casestr(data, "test")); } + +UTEST(utf8casestr, empty) { ASSERT_EQ(data, utf8casestr(data, "")); } + +UTEST(utf8casestr, partial) { + ASSERT_EQ(haystack + 2, utf8casestr(haystack, needle)); +} + +UTEST(utf8casestr, endfail) { + ASSERT_EQ((void *)0, utf8casestr(haystack, endfailneedle)); +} + +UTEST(utf8casestr, latin) { + ASSERT_EQ(lowersStr, utf8casestr(lowersStr, uppersStr)); +} + +UTEST(utf8chr, a) { ASSERT_EQ(data + 21, utf8chr(data, 0x3bc)); } + +UTEST(utf8chr, b) { ASSERT_EQ(NULL, utf8chr(data, 0x20ac)); } + +UTEST(utf8chr, null_terminator) { ASSERT_EQ(data + 104, utf8chr(data, '\0')); } + +UTEST(utf8chr, 0x20) { ASSERT_EQ(data + 12, utf8chr(data, 0x20)); } + +UTEST(utf8cmp, lt) { ASSERT_LT(0, utf8cmp(data, lt)); } + +UTEST(utf8cmp, eq) { ASSERT_EQ(0, utf8cmp(data, data)); } + +UTEST(utf8cmp, gt) { ASSERT_GT(0, utf8cmp(data, gt)); } + +UTEST(utf8cpy, data) { + char cpy[512] = {'\0'}; + + ASSERT_EQ(53, utf8len(utf8cpy(cpy, data))); +} + +// Matches \xce\x93 \xce\xb1 \xce\xb6 \xce\xad \xce\xb5 \xcf\x82 \x20 \xce\xba +// \xce\xb1 +UTEST(utf8spn, spn) { ASSERT_EQ(9, utf8spn(data, spn)); } + +UTEST(utf8spn, data) { ASSERT_EQ(53, utf8spn(data, data)); } + +UTEST(utf8spn, ascii) { ASSERT_EQ(0, utf8spn(data, "ab")); } + +UTEST(utf8spn, spnasciisearch) { + ASSERT_EQ(2, utf8spn(spnasciisearch, spnasciidelims)); +} + +UTEST(utf8cspn, spn) { ASSERT_EQ(0, utf8cspn(data, spn)); } + +UTEST(utf8cspn, data) { ASSERT_EQ(0, utf8cspn(data, data)); } + +UTEST(utf8cspn, ascii) { ASSERT_EQ(53, utf8cspn(data, "ab")); } + +UTEST(utf8cspn, cspnmultisearch) { + ASSERT_EQ(12, utf8cspn(cspnmultisearch, cspnmultidelims)); +} + +UTEST(utf8rchr, a) { ASSERT_EQ(data + 21, utf8rchr(data, 0x3bc)); } + +UTEST(utf8rchr, b) { ASSERT_EQ(NULL, utf8rchr(data, 0x20ac)); } + +UTEST(utf8rchr, null_terminator) { + ASSERT_EQ(data + 104, utf8rchr(data, '\0')); +} + +UTEST(utf8rchr, 0x20) { ASSERT_EQ(data + 90, utf8rchr(data, 0x20)); } + +UTEST(utf8rchr, overrun) { + const char ascii[] = "Hello\0Hello "; + ASSERT_EQ(4, utf8rchr(ascii, 'o') - ascii); +} + +UTEST(utf8rchr, underrun) { + const char ascii[] = "Helloo"; + ASSERT_EQ(5, utf8rchr(ascii, 'o') - ascii); +} + +UTEST(utf8dup, data) { + void *const dup = utf8dup(data); + ASSERT_TRUE(dup); + ASSERT_EQ(53, utf8len(dup)); + free(dup); +} + +UTEST(utf8dup, ascii) { + void *const dup = utf8dup("ab"); + ASSERT_TRUE(dup); + ASSERT_EQ(2, utf8len(dup)); + free(dup); +} + +UTEST(utf8dup, empty) { + void *const dup = utf8dup(""); + ASSERT_TRUE(dup); + ASSERT_EQ(0, utf8len(dup)); + free(dup); +} + +UTEST(utf8ndup, ascii) { + void *const dup = utf8ndup("1234567890", 4); + ASSERT_TRUE(dup); + ASSERT_EQ(4, utf8len(dup)); + free(dup); +} + +UTEST(utf8ndup, ascii_larger) { + void *const dup = utf8ndup("1234567890", 100); + ASSERT_TRUE(dup); + ASSERT_EQ(10, utf8len(dup)); + free(dup); +} + +static utf8_int8_t *allocate_from_buffer(utf8_int8_t *user_data, size_t n) { + return user_data; +} + +UTEST(utf8dup_ex, ascii) { + char user_data[1024]; + void *const dup = utf8dup_ex("1234567890", allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(10, utf8len(dup)); +} + +UTEST(utf8ndup_ex, ascii) { + char user_data[1024]; + void *const dup = + utf8ndup_ex("1234567890", 4, allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(4, utf8len(dup)); +} + +UTEST(utf8size, data) { ASSERT_EQ(105, utf8size(data)); } + +UTEST(utf8size, ascii) { ASSERT_EQ(3, utf8size("ab")); } + +UTEST(utf8size, empty) { ASSERT_EQ(1, utf8size("")); } + +UTEST(utf8size_lazy, data) { ASSERT_EQ(104, utf8size_lazy(data)); } + +UTEST(utf8size_lazy, ascii) { ASSERT_EQ(2, utf8size_lazy("ab")); } + +UTEST(utf8size_lazy, empty) { ASSERT_EQ(0, utf8size_lazy("")); } + +UTEST(utf8nsize_lazy, data) { ASSERT_EQ(50, utf8nsize_lazy(data, 50)); } + +UTEST(utf8nsize_lazy, ascii) { ASSERT_EQ(2, utf8nsize_lazy("ab", 50)); } + +UTEST(utf8nsize_lazy, empty) { ASSERT_EQ(0, utf8nsize_lazy("", 50)); } + +UTEST(utf8valid, a) { + char invalid[6]; + + invalid[0] = '\xf0'; + invalid[1] = '\x8f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, b) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, c) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, d) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\x3f'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, e) { + char invalid[6]; + + invalid[0] = '\xe0'; + invalid[1] = '\x9f'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, f) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, g) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, h) { + char invalid[6]; + + invalid[0] = '\xc1'; + invalid[1] = '\xbf'; + invalid[2] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, i) { + char invalid[6]; + + invalid[0] = '\xdf'; + invalid[1] = '\x3f'; + invalid[2] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, j) { + char invalid[6]; + + invalid[0] = '\x80'; + invalid[1] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, k) { + char invalid[6]; + + invalid[0] = '\xf8'; + invalid[1] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, l) { + char invalid[6]; + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\xbf'; + invalid[5] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, m) { + char invalid[6]; + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, n) { + char invalid[6]; + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\0'; + + ASSERT_EQ(invalid, utf8valid(invalid)); +} + +UTEST(utf8valid, data) { ASSERT_EQ(NULL, utf8valid(data)); } + +UTEST(utf8valid, ascii) { ASSERT_EQ(NULL, utf8valid("ab")); } + +UTEST(utf8valid, empty) { ASSERT_EQ(NULL, utf8valid("")); } + +UTEST(utf8nvalid, a) { + char valid[3]; + + const char *invalid = valid; + const size_t invalid_size = 1; + + valid[0] = '\xc2'; + valid[1] = '\x80'; + valid[2] = '\0'; + + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, b) { + char valid[4]; + + const char *invalid = valid; + const size_t invalid_size = 2; + + valid[0] = '\xe0'; + valid[1] = '\x80'; + valid[2] = '\x80'; + valid[3] = '\0'; + + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, c) { + char valid[5]; + + const char *invalid = valid; + const size_t invalid_size = 3; + + valid[0] = '\xf0'; + valid[1] = '\x80'; + valid[2] = '\x80'; + valid[3] = '\x80'; + valid[4] = '\0'; + ASSERT_EQ(invalid, utf8nvalid(valid, invalid_size)); +} + +UTEST(utf8nvalid, data) { ASSERT_EQ(NULL, utf8nvalid(data, 105)); } + +UTEST(utf8nvalid, ascii) { ASSERT_EQ(NULL, utf8nvalid("ab", 3)); } + +UTEST(utf8nvalid, empty) { ASSERT_EQ(NULL, utf8nvalid("", 1)); } + +UTEST(utf8ncat, ascii_cat_data) { + char cat[512] = {'\0'}; + cat[0] = 'a'; + cat[1] = '\0'; + ASSERT_EQ(2, utf8len(utf8ncat(cat, data, 2))); +} + +UTEST(utf8ncat, cat_data) { + char cat[512] = {'\0'}; + ASSERT_EQ(53, utf8len(utf8ncat(cat, data, 40000))); +} + +UTEST(utf8ncat, bad_cat) { + char cat[512] = {'\0'}; + ASSERT_EQ(cat, utf8valid(utf8ncat(cat, data, 1))); +} + +UTEST(utf8ncat, zero_n) { + char cat[512] = {'\0'}; + ASSERT_EQ(NULL, utf8valid(utf8ncat(cat, data, 0))); +} + +UTEST(utf8ncmp, lt_large) { ASSERT_LT(0, utf8ncmp(data, lt, 4000)); } + +UTEST(utf8ncmp, lt_small) { ASSERT_EQ(0, utf8ncmp(data, lt, 7)); } + +UTEST(utf8ncmp, eq_large) { ASSERT_EQ(0, utf8ncmp(data, data, 4000)); } + +UTEST(utf8ncmp, eq_small) { ASSERT_EQ(0, utf8ncmp(data, data, 7)); } + +UTEST(utf8ncmp, gt_large) { ASSERT_GT(0, utf8ncmp(data, gt, 4000)); } + +UTEST(utf8ncmp, gt_small) { ASSERT_EQ(0, utf8ncmp(data, gt, 7)); } + +UTEST(utf8ncpy, data_null_terminated) { + char cpy[512] = {'\0'}; + ASSERT_EQ('\0', *((char *)utf8ncpy(cpy, data, 106) + 105)); +} + +UTEST(utf8ncpy, data) { + char cpy[512] = {'\0'}; + ASSERT_EQ(53, utf8len(utf8ncpy(cpy, data, 105))); +} + +UTEST(utf8ncpy, check_no_buffer_overflow) { + utf8_int32_t i; + char buffer[11] = {0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}; + ASSERT_EQ(buffer, utf8ncpy(buffer, "foo", 10)); + + ASSERT_EQ('f', buffer[0]); + ASSERT_EQ('o', buffer[1]); + ASSERT_EQ('o', buffer[2]); + + for (i = 3; 10 != i; i++) { + ASSERT_EQ(0, buffer[i]); + } + + ASSERT_EQ((char)0xdd, buffer[10]); +} + +UTEST(utf8ncpy, check_no_n_overflow) { + char buffer[4] = {1, 2, 3, 4}; + ASSERT_EQ(buffer, utf8ncpy(buffer, "foo", 2)); + + ASSERT_EQ('f', buffer[0]); + ASSERT_EQ('o', buffer[1]); + ASSERT_EQ(3, buffer[2]); + ASSERT_EQ(4, buffer[3]); +} + +UTEST(utf8ncpy, truncated_copy_valid) { + char cpy1[32] = {'\0'}; + char cpy2[3] = {'\0'}; + char cpy3[1] = {'\0'}; + + utf8ncpy(cpy1, data, 32); + ASSERT_EQ(NULL, utf8valid(cpy1)); + + utf8ncpy(cpy2, data, 3); + ASSERT_EQ(NULL, utf8valid(cpy2)); + + utf8ncpy(cpy3, data, 1); + ASSERT_EQ(NULL, utf8valid(cpy3)); +} + +UTEST(utf8ncpy, truncated_copy_null_terminated) { + char cpy1[32] = {'\0'}; + char cpy2[2] = {'\0'}; + char cpy3[3] = {'\0'}; + + utf8ncpy(cpy1, data, 32); + ASSERT_EQ('\0', cpy1[31]); + + utf8ncpy(cpy2, data, 2); + ASSERT_EQ('\0', cpy2[0]); + + utf8ncpy(cpy3, data, 3); + ASSERT_EQ('\0', cpy3[2]); +} + +UTEST(utf8pbrk, pbrk) { ASSERT_EQ(data + 8, utf8pbrk(data, pbrk)); } + +UTEST(utf8pbrk, data) { ASSERT_EQ(data, utf8pbrk(data, data)); } + +UTEST(utf8casecmp, ascii) { ASSERT_EQ(0, utf8casecmp(ascii1, ascii2)); } +UTEST(utf8casecmp, latin_upvslow) { + ASSERT_EQ(0, utf8casecmp(lowersStr, uppersStr)); +} +UTEST(utf8casecmp, latin_lowvsup) { + ASSERT_EQ(0, utf8casecmp(uppersStr, lowersStr)); +} + +UTEST(utf8casecmp, allascii) { + ASSERT_EQ(0, utf8casecmp(allascii1, allascii2)); +} + +UTEST(utf8casecmp, data_lt) { ASSERT_LT(0, utf8casecmp(data, lt)); } + +UTEST(utf8casecmp, data_eq) { ASSERT_EQ(0, utf8casecmp(data, data)); } + +UTEST(utf8casecmp, data_gt) { ASSERT_GT(0, utf8casecmp(data, gt)); } + +UTEST(utf8ncasecmp, lt_large) { ASSERT_LT(0, utf8ncasecmp(data, lt, 4000)); } + +UTEST(utf8ncasecmp, lt_small) { ASSERT_EQ(0, utf8ncasecmp(data, lt, 7)); } + +UTEST(utf8ncasecmp, eq_large) { ASSERT_EQ(0, utf8ncasecmp(data, data, 4000)); } + +UTEST(utf8ncasecmp, eq_small) { ASSERT_EQ(0, utf8ncasecmp(data, data, 7)); } + +UTEST(utf8ncasecmp, gt_large) { ASSERT_GT(0, utf8ncasecmp(data, gt, 4000)); } + +UTEST(utf8ncasecmp, gt_small) { ASSERT_EQ(0, utf8ncasecmp(data, gt, 7)); } + +UTEST(utf8ncasecmp, ascii) { ASSERT_EQ(0, utf8ncasecmp(ascii1, ascii2, 4)); } +UTEST(utf8ncasecmp, latin_upvslow) { + ASSERT_EQ(0, utf8ncasecmp(lowersStr, uppersStr, 120)); +} +UTEST(utf8ncasecmp, latin_lowvsup) { + ASSERT_EQ(0, utf8ncasecmp(uppersStr, lowersStr, 120)); +} + +UTEST(utf8ncasecmp, basic_ascii) { + ASSERT_EQ(-15, utf8ncasecmp(".gdoc", ".GSHeeT", 5)); + ASSERT_EQ(-4, utf8ncasecmp(".gsheet", ".gSLiDe", 7)); + +#ifndef _MSC_VER + ASSERT_EQ(strcasecmp(".gdoc", ".GSHeeT"), + utf8ncasecmp(".gdoc", ".GSHeeT", 5)); + ASSERT_EQ(strcasecmp(".gsheet", ".gSLiDe"), + utf8ncasecmp(".gsheet", ".gSLiDe", 7)); +#endif +} + +UTEST(utf8ncasecmp, latin_extended_a) { + ASSERT_EQ(96, utf8ncasecmp("Camón Romasan", "camu", 4)); +} + +UTEST(utf8codepoint, data) { + utf8_int32_t codepoint; + void *v; + size_t expected_length = utf8len(data) - 1; + for (v = utf8codepoint(data, &codepoint); codepoint; + v = utf8codepoint(v, &codepoint)) { + ASSERT_EQ(expected_length, utf8len(v)); + expected_length -= 1; + } +} + +UTEST(utf8codepointcalcsize, data) { + const char *v; + // No -1 here since we start at the beginning + size_t expected_length = utf8len(data); + for (v = data; *v; v += utf8codepointcalcsize(v)) { + ASSERT_EQ(expected_length, utf8len(v)); + expected_length -= 1; + } +} + +UTEST(utf8codepointsize, size_1) { ASSERT_EQ(1, utf8codepointsize('A')); } + +UTEST(utf8codepointsize, size_4) { ASSERT_EQ(4, utf8codepointsize(0x20C78)); } + +UTEST(utf8catcodepoint, data) { + char buffer[129]; + char *p = buffer; + long cp; + int i; + memset(buffer, 0, 129); + for (i = 0; i < 128; i++) { + cp = (i % 2 == 0 ? 'A' : 0x20C78); + p = utf8catcodepoint(p, cp, 128 - (p - buffer)); + if (!p) { + break; + } + } + ASSERT_EQ(51, utf8len(buffer)); +} + +UTEST(utf8islower, upper) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(0, utf8islower(lowupPairs[i].upper)); + } +} + +UTEST(utf8islower, lower) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(1, utf8islower(lowupPairs[i].lower)); + } +} + +UTEST(utf8isupper, upper) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(1, utf8isupper(lowupPairs[i].upper)); + } +} + +UTEST(utf8isupper, lower) { + utf8_int32_t i; + + for (i = 0; 0 != lowupPairs[i].lower; i++) { + ASSERT_EQ(0, utf8isupper(lowupPairs[i].lower)); + } +} + +UTEST(utf8lwr, ascii) { + size_t sz; + char *str; + sz = strlen(ascii1); + str = (char *)malloc(sz + 1); + memcpy(str, ascii1, sz + 1); + utf8lwr(str); + ASSERT_EQ(0, strcmp(str, "i like goats yarhar.")); + free(str); +} + +UTEST(utf8lwr, latin_lower) { + size_t sz; + void *str; + sz = utf8size(lowersStr); + str = malloc(sz); + memcpy(str, lowersStr, sz); + utf8lwr(str); + ASSERT_EQ(0, utf8cmp(str, lowersStr)); + free(str); +} + +UTEST(utf8lwr, latin_upper) { + size_t sz; + void *str; + sz = utf8size(uppersStr); + str = malloc(sz); + memcpy(str, uppersStr, sz); + utf8lwr(str); + ASSERT_EQ(0, utf8cmp(str, lowersStr)); + free(str); +} + +UTEST(utf8upr, ascii) { + size_t sz; + char *str; + sz = strlen(ascii1); + str = (char *)malloc(sz + 1); + memcpy(str, ascii1, sz + 1); + utf8upr(str); + ASSERT_EQ(0, strcmp(str, "I LIKE GOATS YARHAR.")); + free(str); +} + +UTEST(utf8upr, latin_lower) { + size_t sz; + void *str; + sz = utf8size(lowersStr); + str = malloc(sz); + memcpy(str, lowersStr, sz); + utf8upr(str); + ASSERT_EQ(0, utf8cmp(str, uppersStr)); + free(str); +} + +UTEST(utf8upr, latin_upper) { + size_t sz; + void *str; + sz = utf8size(uppersStr); + str = malloc(sz); + memcpy(str, uppersStr, sz); + utf8upr(str); + ASSERT_EQ(0, utf8cmp(str, uppersStr)); + free(str); +} + +UTEST(utf8casecmp, basic_ascii) { + ASSERT_EQ(-15, utf8casecmp(".gdoc", ".GSHeeT")); + ASSERT_EQ(-4, utf8casecmp(".gsheet", ".gSLiDe")); + +#ifndef _MSC_VER + ASSERT_EQ(strcasecmp(".gdoc", ".GSHeeT"), utf8casecmp(".gdoc", ".GSHeeT")); + ASSERT_EQ(strcasecmp(".gsheet", ".gSLiDe"), + utf8casecmp(".gsheet", ".gSLiDe")); +#endif +} + +UTEST(utf8lwr, greek_capital_theta) { + const char ref[] = {'\xce', '\xb8', '\xce', '\xb8', '\xce', + '\xb8', '\xcf', '\x91', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + utf8lwr(str); + + ASSERT_EQ(0, utf8cmp(str, ref)); +} + +UTEST(utf8upr, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + utf8upr(str); + + ASSERT_EQ(0, utf8cmp(str, ref)); +} + +UTEST(utf8casecmp, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + ASSERT_EQ(0, utf8casecmp(ref, str)); +} + +UTEST(utf8ncasecmp, greek_capital_theta) { + const char ref[] = {'\xcf', '\xb4', '\xce', '\x98', '\xce', + '\x98', '\xce', '\x98', '\0'}; + char str[] = {'\xcf', '\xb4', '\xce', '\xb8', '\xce', + '\x98', '\xcf', '\x91', '\0'}; + + ASSERT_EQ(0, utf8ncasecmp(ref, str, 8)); +} + +UTEST(utf8rcodepoint, ascii) { + utf8_int32_t codepoint; + + ASSERT_EQ(ascii1, utf8rcodepoint(ascii1 + 1, &codepoint)); + + ASSERT_EQ(ascii1[1], codepoint); +} + +UTEST(utf8rcodepoint, latin) { + utf8_int32_t codepoint; + + ASSERT_EQ(data, utf8rcodepoint(data + 2, &codepoint)); + + ASSERT_EQ(0x3B1, codepoint); +} + +UTEST(utf8makevalid, a) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf0'; + invalid[1] = '\x8f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xef'); +} + +UTEST(utf8makevalid, b) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, c) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, d) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, e) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xe0'; + invalid[1] = '\x9f'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xdf'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\0'); +} + +UTEST(utf8makevalid, f) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\x3f'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, g) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, h) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xc1'; + invalid[1] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\x7f'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, i) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\x3f'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '?'); + ASSERT_EQ(invalid[2], '\0'); +} + +UTEST(utf8makevalid, j) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\x80'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, k) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf8'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '?'); + ASSERT_EQ(invalid[1], '\0'); +} + +UTEST(utf8makevalid, l) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xf1'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + invalid[4] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xf1'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\xbf'); + ASSERT_EQ(invalid[3], '\xbf'); + ASSERT_EQ(invalid[4], '?'); + ASSERT_EQ(invalid[5], '\0'); +} + +UTEST(utf8makevalid, m) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xef'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + invalid[3] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xef'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '\xbf'); + ASSERT_EQ(invalid[3], '?'); + ASSERT_EQ(invalid[4], '\0'); +} + +UTEST(utf8makevalid, n) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + + ASSERT_EQ(0, utf8makevalid(invalid, '?')); + + ASSERT_EQ(invalid[0], '\xdf'); + ASSERT_EQ(invalid[1], '\xbf'); + ASSERT_EQ(invalid[2], '?'); + ASSERT_EQ(invalid[3], '\0'); +} + +UTEST(utf8makevalid, invalid_replacement) { + char invalid[6]; + memset(invalid, 0, 6); + + invalid[0] = '\xdf'; + invalid[1] = '\xbf'; + invalid[2] = '\xbf'; + + ASSERT_NE(0, utf8makevalid(invalid, 0x80)); +} + +UTEST(utf8nvalid, exactly_2_bytes) { + const char terminated[] = "\xc2\xa3"; + ASSERT_EQ(utf8nvalid(terminated, 2), NULL); +} + +UTEST(utf8nvalid, exactly_3_bytes) { + const char terminated[] = "\xe1\xbd\xb6"; + ASSERT_EQ(utf8nvalid(terminated, 3), NULL); +} + +UTEST(utf8nvalid, exactly_4_bytes) { + const char terminated[] = "\xf0\x90\x8d\x88"; + ASSERT_EQ(utf8nvalid(terminated, 4), NULL); +} + +UTEST_MAIN(); diff --git a/src/utf8/test/no_malloc.c b/src/utf8/test/no_malloc.c new file mode 100644 index 0000000..b0d4a05 --- /dev/null +++ b/src/utf8/test/no_malloc.c @@ -0,0 +1,64 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +// include the unit testing framework +#include "utest.h" + +// include the header we are testing +#define UTF8_NO_STD_MALLOC +#include "utf8.h" + +UTEST(no_malloc_utf8dup, ascii) { + void *const dup = utf8dup("1234567890"); + ASSERT_FALSE(dup); +} + +UTEST(no_malloc_utf8ndup, ascii) { + void *const dup = utf8ndup("1234567890", 4); + ASSERT_FALSE(dup); +} + +static utf8_int8_t *allocate_from_buffer(utf8_int8_t *user_data, size_t n) { + return user_data; +} + +UTEST(no_malloc_utf8dup_ex, ascii) { + char user_data[1024]; + void *const dup = utf8dup_ex("1234567890", allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(10, utf8len(dup)); +} + +UTEST(no_malloc_utf8ndup_ex, ascii) { + char user_data[1024]; + void *const dup = + utf8ndup_ex("1234567890", 4, allocate_from_buffer, user_data); + ASSERT_TRUE(dup); + ASSERT_EQ(dup, user_data); + ASSERT_EQ(4, utf8len(dup)); +} + +UTEST_MAIN(); diff --git a/src/utf8/test/test.c b/src/utf8/test/test.c new file mode 100644 index 0000000..397bcdc --- /dev/null +++ b/src/utf8/test/test.c @@ -0,0 +1,33 @@ +/* This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include "utf8.h" + +int main(const int argc, const char *const argv[]) { + (void)argc; + (void)argv; + return 0; +} diff --git a/src/utf8/test/test.cpp b/src/utf8/test/test.cpp new file mode 100644 index 0000000..81900b1 --- /dev/null +++ b/src/utf8/test/test.cpp @@ -0,0 +1,78 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +#include "utf8.h" + +// We don't care about the results. We only want to check compilation + +#if defined(__clang__) +#pragma clang diagnostic push + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +#if defined(__cplusplus) && __cplusplus >= 201402L +constexpr void test() { + constexpr utf8_int8_t in_str[20]{}; + constexpr utf8_int32_t in_chr{}; + utf8_int32_t out_chr{}; + + utf8codepoint(in_str, &out_chr); + utf8rcodepoint(in_str + 1, &out_chr); + static_assert(utf8chr(in_str, utf8_int32_t{}), "utf8 constexpr fail"); + static_assert(utf8cmp(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8cspn(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8len(in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8nlen(in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8ncmp(in_str, in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8pbrk(in_str, in_str) == nullptr, "utf8 constexpr fail"); + static_assert(utf8rchr(in_str, 1) == nullptr, "utf8 constexpr fail"); + static_assert(utf8spn(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8str(in_str, in_str), "utf8 constexpr fail"); + static_assert(utf8casecmp(in_str, in_str) == 0, "utf8 constexpr fail"); + static_assert(utf8ncasecmp(in_str, in_str, 1) == 0, "utf8 constexpr fail"); + static_assert(utf8casestr(in_str, in_str), "utf8 constexpr fail"); + static_assert(utf8size(in_str), "utf8 constexpr fail"); + static_assert(utf8size_lazy(in_str) == 0, "utf8 constexpr faillazy;"); + static_assert(utf8nsize_lazy(in_str, 1) == 0, "utf8 constexpr faillazy;"); + static_assert(utf8valid(in_str) == nullptr, "utf8 constexpr fail"); + static_assert(utf8nvalid(in_str, 1) == nullptr, "utf8 constexpr fail"); + static_assert(utf8codepointsize(in_chr), "utf8 constexpr fail"); + static_assert(utf8isupper(in_chr) == false, "utf8 constexpr fail"); + static_assert(utf8islower(in_chr) == false, "utf8 constexpr fail"); + static_assert(utf8lwrcodepoint(in_chr) == in_chr, "utf8 constexpr fail"); + static_assert(utf8uprcodepoint(in_chr) == in_chr, "utf8 constexpr fail"); +} +#else +static void test() {} +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +int main() { test(); } diff --git a/src/utf8/test/utest.h b/src/utf8/test/utest.h new file mode 100644 index 0000000..8767600 --- /dev/null +++ b/src/utf8/test/utest.h @@ -0,0 +1,1668 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/utest.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_UTEST_H_INCLUDED +#define SHEREDOM_UTEST_H_INCLUDED + +#ifdef _MSC_VER +/* + Disable warning about not inlining 'inline' functions. +*/ +#pragma warning(disable : 4710) + +/* + Disable warning about inlining functions that are not marked 'inline'. +*/ +#pragma warning(disable : 4711) + +/* + Disable warning for alignment padding added +*/ +#pragma warning(disable : 4820) + +#if _MSC_VER > 1900 +/* + Disable warning about preprocessor macros not being defined in MSVC headers. +*/ +#pragma warning(disable : 4668) + +/* + Disable warning about no function prototype given in MSVC headers. +*/ +#pragma warning(disable : 4255) + +/* + Disable warning about pointer or reference to potentially throwing function. +*/ +#pragma warning(disable : 5039) + +/* + Disable warning about macro expansion producing 'defined' has undefined + behavior. +*/ +#pragma warning(disable : 5105) +#endif + +#if _MSC_VER > 1930 +/* + Disable warning about 'const' variable is not used. +*/ +#pragma warning(disable : 5264) +#endif + +#pragma warning(push, 1) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +typedef __int64 utest_int64_t; +typedef unsigned __int64 utest_uint64_t; +typedef unsigned __int32 utest_uint32_t; +#else +#include +typedef int64_t utest_int64_t; +typedef uint64_t utest_uint64_t; +typedef uint32_t utest_uint32_t; +#endif + +#include +#include +#include +#include +#include + +#if defined(__cplusplus) +#if defined(_MSC_VER) && !defined(_CPPUNWIND) +/* We're on MSVC and the compiler is compiling without exception support! */ +#elif !defined(_MSC_VER) && !defined(__EXCEPTIONS) +/* We're on a GCC/Clang compiler that doesn't have exception support! */ +#else +#define UTEST_HAS_EXCEPTIONS 1 +#endif +#endif + +#if defined(UTEST_HAS_EXCEPTIONS) +#include +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__cplusplus) +#define UTEST_C_FUNC extern "C" +#else +#define UTEST_C_FUNC +#endif + +#define UTEST_TEST_PASSED (0) +#define UTEST_TEST_FAILURE (1) +#define UTEST_TEST_SKIPPED (2) + +#if defined(__TINYC__) +#define UTEST_ATTRIBUTE(a) __attribute((a)) +#else +#define UTEST_ATTRIBUTE(a) __attribute__((a)) +#endif + +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) + +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#endif + +#if defined(_WINDOWS_) || defined(_WINDOWS_H) +typedef LARGE_INTEGER utest_large_integer; +#else +// use old QueryPerformanceCounter definitions (not sure is this needed in some +// edge cases or not) on Win7 with VS2015 these extern declaration cause "second +// C linkage of overloaded function not allowed" error +typedef union { + struct { + unsigned long LowPart; + long HighPart; + } DUMMYSTRUCTNAME; + struct { + unsigned long LowPart; + long HighPart; + } u; + utest_int64_t QuadPart; +} utest_large_integer; + +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceCounter( + utest_large_integer *); +UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceFrequency( + utest_large_integer *); + +#if defined(__MINGW64__) || defined(__MINGW32__) +#pragma GCC diagnostic pop +#endif +#endif + +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) +/* + slightly obscure include here - we need to include glibc's features.h, but + we don't want to just include a header that might not be defined for other + c libraries like musl. Instead we include limits.h, which we know on all + glibc distributions includes features.h +*/ +#include + +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) +#include + +#if ((2 < __GLIBC__) || ((2 == __GLIBC__) && (17 <= __GLIBC_MINOR__))) +/* glibc is version 2.17 or above, so we can just use clock_gettime */ +#define UTEST_USE_CLOCKGETTIME +#else +#include +#include +#endif +#else // Other libc implementations +#include +#define UTEST_USE_CLOCKGETTIME +#endif + +#elif defined(__APPLE__) +#include +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define UTEST_PRId64 "I64d" +#define UTEST_PRIu64 "I64u" +#else +#include + +#define UTEST_PRId64 PRId64 +#define UTEST_PRIu64 PRIu64 +#endif + +#if defined(__cplusplus) +#define UTEST_INLINE inline + +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + +#define UTEST_INITIALIZER(f) \ + struct f##_cpp_struct { \ + f##_cpp_struct(); \ + }; \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS static f##_cpp_struct \ + f##_cpp_global UTEST_INITIALIZER_END_DISABLE_WARNINGS; \ + f##_cpp_struct::f##_cpp_struct() +#elif defined(_MSC_VER) +#define UTEST_INLINE __forceinline + +#if defined(_WIN64) +#define UTEST_SYMBOL_PREFIX +#else +#define UTEST_SYMBOL_PREFIX "_" +#endif + +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wmissing-variable-declarations\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + +#pragma section(".CRT$XCU", read) +#define UTEST_INITIALIZER(f) \ + static void __cdecl f(void); \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")) \ + UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * \ + f##_)(void) = f; \ + UTEST_INITIALIZER_END_DISABLE_WARNINGS \ + static void __cdecl f(void) +#else +#if defined(__linux__) +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif +#endif + +#define __STDC_FORMAT_MACROS 1 + +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic pop +#endif +#endif +#endif + +#define UTEST_INLINE inline + +#define UTEST_INITIALIZER(f) \ + static void f(void) UTEST_ATTRIBUTE(constructor); \ + static void f(void) +#endif + +#if defined(__cplusplus) +#define UTEST_CAST(type, x) static_cast(x) +#define UTEST_PTR_CAST(type, x) reinterpret_cast(x) +#define UTEST_EXTERN extern "C" +#define UTEST_NULL NULL +#else +#define UTEST_CAST(type, x) ((type)(x)) +#define UTEST_PTR_CAST(type, x) ((type)(x)) +#define UTEST_EXTERN extern +#define UTEST_NULL 0 +#endif + +#ifdef _MSC_VER +/* + io.h contains definitions for some structures with natural padding. This is + uninteresting, but for some reason MSVC's behaviour is to warn about + including this system header. That *is* interesting +*/ +#pragma warning(disable : 4820) +#pragma warning(push, 1) +#include +#pragma warning(pop) +#define UTEST_COLOUR_OUTPUT() (_isatty(_fileno(stdout))) +#else +#if defined(__EMSCRIPTEN__) +#include +#define UTEST_COLOUR_OUTPUT() false +#else +#include +#define UTEST_COLOUR_OUTPUT() (isatty(STDOUT_FILENO)) +#endif +#endif + +static UTEST_INLINE void *utest_realloc(void *const pointer, size_t new_size) { + void *const new_pointer = realloc(pointer, new_size); + + if (UTEST_NULL == new_pointer) { + free(new_pointer); + } + + return new_pointer; +} + +static UTEST_INLINE utest_int64_t utest_ns(void) { +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) + utest_large_integer counter; + utest_large_integer frequency; + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&frequency); + return UTEST_CAST(utest_int64_t, + (counter.QuadPart * 1000000000) / frequency.QuadPart); +#elif defined(__linux__) && defined(__STRICT_ANSI__) + return UTEST_CAST(utest_int64_t, clock()) * 1000000000 / CLOCKS_PER_SEC; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \ + defined(__HAIKU__) + struct timespec ts; +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(__HAIKU__) + timespec_get(&ts, TIME_UTC); +#else + const clockid_t cid = CLOCK_REALTIME; +#if defined(UTEST_USE_CLOCKGETTIME) + clock_gettime(cid, &ts); +#else + syscall(SYS_clock_gettime, cid, &ts); +#endif +#endif + return UTEST_CAST(utest_int64_t, ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; +#elif __APPLE__ + return UTEST_CAST(utest_int64_t, clock_gettime_nsec_np(CLOCK_UPTIME_RAW)); +#elif __EMSCRIPTEN__ + return emscripten_performance_now() * 1000000.0; +#else +#error Unsupported platform! +#endif +} + +typedef void (*utest_testcase_t)(int *, size_t); + +struct utest_test_state_s { + utest_testcase_t func; + size_t index; + char *name; +}; + +struct utest_state_s { + struct utest_test_state_s *tests; + size_t tests_length; + FILE *output; +}; + +/* extern to the global state utest needs to execute */ +UTEST_EXTERN struct utest_state_s utest_state; + +#if defined(_MSC_VER) +#define UTEST_WEAK __forceinline +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define UTEST_WEAK static UTEST_ATTRIBUTE(used) +#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__) +#define UTEST_WEAK UTEST_ATTRIBUTE(weak) +#else +#error Non clang, non gcc, non MSVC, non tcc compiler found! +#endif + +#if defined(_MSC_VER) +#define UTEST_UNUSED +#else +#define UTEST_UNUSED UTEST_ATTRIBUTE(unused) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#define UTEST_PRINTF(...) \ + if (utest_state.output) { \ + fprintf(utest_state.output, __VA_ARGS__); \ + } \ + printf(__VA_ARGS__) +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#ifdef _MSC_VER +#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) +#else +#define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(__cplusplus) +/* if we are using c++ we can use overloaded methods (its in the language) */ +#define UTEST_OVERLOADABLE +#elif defined(__clang__) +/* otherwise, if we are using clang with c - use the overloadable attribute */ +#define UTEST_OVERLOADABLE UTEST_ATTRIBUTE(overloadable) +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#include + +template ::value> +struct utest_type_deducer final { + static void _(const T t); +}; + +template <> struct utest_type_deducer { + static void _(const signed char c) { + UTEST_PRINTF("%d", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned char c) { + UTEST_PRINTF("%u", static_cast(c)); + } +}; + +template <> struct utest_type_deducer { + static void _(const short s) { UTEST_PRINTF("%d", static_cast(s)); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned short s) { + UTEST_PRINTF("%u", static_cast(s)); + } +}; + +template <> struct utest_type_deducer { + static void _(const float f) { UTEST_PRINTF("%f", static_cast(f)); } +}; + +template <> struct utest_type_deducer { + static void _(const double d) { UTEST_PRINTF("%f", d); } +}; + +template <> struct utest_type_deducer { + static void _(const long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else + UTEST_PRINTF("%Lf", d); +#endif + } +}; + +template <> struct utest_type_deducer { + static void _(const int i) { UTEST_PRINTF("%d", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned int i) { UTEST_PRINTF("%u", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long i) { UTEST_PRINTF("%ld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long i) { UTEST_PRINTF("%lu", i); } +}; + +template <> struct utest_type_deducer { + static void _(const long long i) { UTEST_PRINTF("%lld", i); } +}; + +template <> struct utest_type_deducer { + static void _(const unsigned long long i) { UTEST_PRINTF("%llu", i); } +}; + +template struct utest_type_deducer { + static void _(const T *t) { + UTEST_PRINTF("%p", static_cast(const_cast(t))); + } +}; + +template struct utest_type_deducer { + static void _(T *t) { UTEST_PRINTF("%p", static_cast(t)); } +}; + +template struct utest_type_deducer { + static void _(const T t) { + UTEST_PRINTF("%llu", static_cast(t)); + } +}; + +template +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const T t) { + utest_type_deducer::_(t); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#elif defined(UTEST_OVERLOADABLE) + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c) { + UTEST_PRINTF("%d", UTEST_CAST(int, c)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c) { + UTEST_PRINTF("%u", UTEST_CAST(unsigned int, c)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f) { + UTEST_PRINTF("%f", UTEST_CAST(double, f)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d) { + UTEST_PRINTF("%f", d); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d) { +#if defined(__MINGW32__) || defined(__MINGW64__) + /* MINGW is weird - doesn't like LF at all?! */ + UTEST_PRINTF("%f", (double)d); +#else + UTEST_PRINTF("%Lf", d); +#endif +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i) { + UTEST_PRINTF("%d", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i) { + UTEST_PRINTF("%u", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i) { + UTEST_PRINTF("%ld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i) { + UTEST_PRINTF("%lu", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p) { + UTEST_PRINTF("%p", p); +} + +/* + long long is a c++11 extension +*/ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || \ + defined(__cplusplus) && (__cplusplus >= 201103L) || \ + (defined(__MINGW32__) || defined(__MINGW64__)) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i) { + UTEST_PRINTF("%lld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void +utest_type_printer(long long unsigned int i) { + UTEST_PRINTF("%llu", i); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !(defined(__MINGW32__) || defined(__MINGW64__)) || \ + defined(__TINYC__) +#define utest_type_printer(val) \ + UTEST_PRINTF(_Generic((val), signed char \ + : "%d", unsigned char \ + : "%u", short \ + : "%d", unsigned short \ + : "%u", int \ + : "%d", long \ + : "%ld", long long \ + : "%lld", unsigned \ + : "%u", unsigned long \ + : "%lu", unsigned long long \ + : "%llu", float \ + : "%f", double \ + : "%f", long double \ + : "%Lf", default \ + : _Generic((val - val), ptrdiff_t \ + : "%p", default \ + : "undef")), \ + (val)) +#else +/* + we don't have the ability to print the values we got, so we create a macro + to tell our users we can't do anything fancy +*/ +#define utest_type_printer(...) UTEST_PRINTF("undef") +#endif + +#if defined(_MSC_VER) +#define UTEST_SURPRESS_WARNING_BEGIN \ + __pragma(warning(push)) __pragma(warning(disable : 4127)) \ + __pragma(warning(disable : 4571)) __pragma(warning(disable : 4130)) +#define UTEST_SURPRESS_WARNING_END __pragma(warning(pop)) +#else +#define UTEST_SURPRESS_WARNING_BEGIN +#define UTEST_SURPRESS_WARNING_END +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define UTEST_AUTO(x) auto +#elif !defined(__cplusplus) + +#if defined(__clang__) +/* clang-format off */ +/* had to disable clang-format here because it malforms the pragmas */ +#define UTEST_AUTO(x) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wgnu-auto-type\"") __auto_type \ + _Pragma("clang diagnostic pop") +/* clang-format on */ +#else +#define UTEST_AUTO(x) __typeof__(x + 0) +#endif + +#else +#define UTEST_AUTO(x) typeof(x + 0) +#endif + +#if defined(__clang__) +#define UTEST_STRNCMP(x, y, size) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdisabled-macro-expansion\"") \ + strncmp(x, y, size) _Pragma("clang diagnostic pop") +#else +#define UTEST_STRNCMP(x, y, size) strncmp(x, y, size) +#endif + +#if defined(_MSC_VER) +#define UTEST_STRNCPY(x, y, size) strcpy_s(x, size, y) +#elif !defined(__clang__) && defined(__GNUC__) +static UTEST_INLINE char * +utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + return strncpy(dst, src, size); +#pragma GCC diagnostic pop +} + +#define UTEST_STRNCPY(x, y, size) utest_strncpy_gcc(x, y, size) +#else +#define UTEST_STRNCPY(x, y, size) strncpy(x, y, size) +#endif + +#define UTEST_SKIP(msg) \ + do { \ + UTEST_PRINTF(" Skipped : '%s'\n", (msg)); \ + *utest_result = UTEST_TEST_SKIPPED; \ + return; \ + } while (0) + +#if defined(__clang__) +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat-pedantic\"") \ + _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \ + UTEST_AUTO(x) xEval = (x); \ + UTEST_AUTO(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + _Pragma("clang diagnostic pop") \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF(")\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#elif defined(__GNUC__) || defined(__TINYC__) +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + UTEST_AUTO(x) xEval = (x); \ + UTEST_AUTO(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : ("); \ + UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF(")\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF(" vs "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#else +#define UTEST_COND(x, y, cond, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + if (!((x)cond(y))) { \ + UTEST_PRINTF("%s:%i: Failure (Expected " #cond " Actual)", __FILE__, \ + __LINE__); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s", msg); \ + } \ + UTEST_PRINTF("\n"); \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END +#endif + +#define EXPECT_EQ(x, y) UTEST_COND(x, y, ==, "", 0) +#define EXPECT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 0) +#define ASSERT_EQ(x, y) UTEST_COND(x, y, ==, "", 1) +#define ASSERT_EQ_MSG(x, y, msg) UTEST_COND(x, y, ==, msg, 1) + +#define EXPECT_NE(x, y) UTEST_COND(x, y, !=, "", 0) +#define EXPECT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 0) +#define ASSERT_NE(x, y) UTEST_COND(x, y, !=, "", 1) +#define ASSERT_NE_MSG(x, y, msg) UTEST_COND(x, y, !=, msg, 1) + +#define EXPECT_LT(x, y) UTEST_COND(x, y, <, "", 0) +#define EXPECT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 0) +#define ASSERT_LT(x, y) UTEST_COND(x, y, <, "", 1) +#define ASSERT_LT_MSG(x, y, msg) UTEST_COND(x, y, <, msg, 1) + +#define EXPECT_LE(x, y) UTEST_COND(x, y, <=, "", 0) +#define EXPECT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 0) +#define ASSERT_LE(x, y) UTEST_COND(x, y, <=, "", 1) +#define ASSERT_LE_MSG(x, y, msg) UTEST_COND(x, y, <=, msg, 1) + +#define EXPECT_GT(x, y) UTEST_COND(x, y, >, "", 0) +#define EXPECT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 0) +#define ASSERT_GT(x, y) UTEST_COND(x, y, >, "", 1) +#define ASSERT_GT_MSG(x, y, msg) UTEST_COND(x, y, >, msg, 1) + +#define EXPECT_GE(x, y) UTEST_COND(x, y, >=, "", 0) +#define EXPECT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 0) +#define ASSERT_GE(x, y) UTEST_COND(x, y, >=, "", 1) +#define ASSERT_GE_MSG(x, y, msg) UTEST_COND(x, y, >=, msg, 1) + +#define UTEST_TRUE(x, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const int xEval = !!(x); \ + if (!(xEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : true\n"); \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_TRUE(x) UTEST_TRUE(x, "", 0) +#define EXPECT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 0) +#define ASSERT_TRUE(x) UTEST_TRUE(x, "", 1) +#define ASSERT_TRUE_MSG(x, msg) UTEST_TRUE(x, msg, 1) + +#define UTEST_FALSE(x, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const int xEval = !!(x); \ + if (xEval) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : false\n"); \ + UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_FALSE(x) UTEST_FALSE(x, "", 0) +#define EXPECT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 0) +#define ASSERT_FALSE(x) UTEST_FALSE(x, "", 1) +#define ASSERT_FALSE_MSG(x, msg) UTEST_FALSE(x, msg, 1) + +#define UTEST_STREQ(x, y, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STREQ(x, y) UTEST_STREQ(x, y, "", 0) +#define EXPECT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 0) +#define ASSERT_STREQ(x, y) UTEST_STREQ(x, y, "", 1) +#define ASSERT_STREQ_MSG(x, y, msg) UTEST_STREQ(x, y, msg, 1) + +#define UTEST_STRNE(x, y, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == strcmp(xEval, yEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \ + UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNE(x, y) UTEST_STRNE(x, y, "", 0) +#define EXPECT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 0) +#define ASSERT_STRNE(x, y) UTEST_STRNE(x, y, "", 1) +#define ASSERT_STRNE_MSG(x, y, msg) UTEST_STRNE(x, y, msg, 1) + +#define UTEST_STRNEQ(x, y, n, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 != UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 0) +#define EXPECT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 0) +#define ASSERT_STRNEQ(x, y, n) UTEST_STRNEQ(x, y, n, "", 1) +#define ASSERT_STRNEQ_MSG(x, y, n, msg) UTEST_STRNEQ(x, y, n, msg, 1) + +#define UTEST_STRNNE(x, y, n, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const char *xEval = (x); \ + const char *yEval = (y); \ + const size_t nEval = UTEST_CAST(size_t, n); \ + if (UTEST_NULL == xEval || UTEST_NULL == yEval || \ + 0 == UTEST_STRNCMP(xEval, yEval, nEval)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \ + UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 0) +#define EXPECT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 0) +#define ASSERT_STRNNE(x, y, n) UTEST_STRNNE(x, y, n, "", 1) +#define ASSERT_STRNNE_MSG(x, y, n, msg) UTEST_STRNNE(x, y, n, msg, 1) + +#define UTEST_NEAR(x, y, epsilon, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const double diff = \ + utest_fabs(UTEST_CAST(double, x) - UTEST_CAST(double, y)); \ + if (diff > UTEST_CAST(double, epsilon) || utest_isnan(diff)) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %f\n", UTEST_CAST(double, x)); \ + UTEST_PRINTF(" Actual : %f\n", UTEST_CAST(double, y)); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 0) +#define EXPECT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 0) +#define ASSERT_NEAR(x, y, epsilon) UTEST_NEAR(x, y, epsilon, "", 1) +#define ASSERT_NEAR_MSG(x, y, epsilon, msg) UTEST_NEAR(x, y, epsilon, msg, 1) + +#if defined(UTEST_HAS_EXCEPTIONS) +#define UTEST_EXCEPTION(x, exception_type, msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + int exception_caught = 0; \ + try { \ + x; \ + } catch (const exception_type &) { \ + exception_caught = 1; \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 0) +#define EXPECT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 0) +#define ASSERT_EXCEPTION(x, exception_type) \ + UTEST_EXCEPTION(x, exception_type, "", 1) +#define ASSERT_EXCEPTION_MSG(x, exception_type, msg) \ + UTEST_EXCEPTION(x, exception_type, msg, 1) + +#define UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, \ + msg, is_assert) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + int exception_caught = 0; \ + char *message_caught = UTEST_NULL; \ + try { \ + x; \ + } catch (const exception_type &e) { \ + const char *const what = e.what(); \ + exception_caught = 1; \ + if (0 != \ + UTEST_STRNCMP(what, exception_message, strlen(exception_message))) { \ + const size_t message_size = strlen(what) + 1; \ + message_caught = UTEST_PTR_CAST(char *, malloc(message_size)); \ + UTEST_STRNCPY(message_caught, what, message_size); \ + } \ + } catch (...) { \ + exception_caught = 2; \ + } \ + if (1 != exception_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \ + UTEST_PRINTF(" Actual : %s\n", (2 == exception_caught) \ + ? "Unexpected exception" \ + : "No exception"); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if (is_assert) { \ + return; \ + } \ + } else if (UTEST_NULL != message_caught) { \ + UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : %s exception with message %s\n", \ + #exception_type, exception_message); \ + UTEST_PRINTF(" Actual message : %s\n", message_caught); \ + if (strlen(msg) > 0) { \ + UTEST_PRINTF(" Message : %s\n", msg); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + free(message_caught); \ + if (is_assert) { \ + return; \ + } \ + } \ + } \ + while (0) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 0) +#define EXPECT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 0) +#define ASSERT_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, "", 1) +#define ASSERT_EXCEPTION_WITH_MESSAGE_MSG(x, exception_type, \ + exception_message, msg) \ + UTEST_EXCEPTION_WITH_MESSAGE(x, exception_type, exception_message, msg, 1) +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunsafe-buffer-usage\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("clang diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif +#elif defined(__GNUC__) && __GNUC__ >= 8 && defined(__cplusplus) +#define UTEST_SURPRESS_WARNINGS_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wclass-memaccess\"") +#define UTEST_SURPRESS_WARNINGS_END _Pragma("GCC diagnostic pop") +#else +#define UTEST_SURPRESS_WARNINGS_BEGIN +#define UTEST_SURPRESS_WARNINGS_END +#endif + +#define UTEST(SET, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##SET##_##NAME(int *utest_result); \ + static void utest_##SET##_##NAME(int *utest_result, size_t utest_index) { \ + (void)utest_index; \ + utest_run_##SET##_##NAME(utest_result); \ + } \ + UTEST_INITIALIZER(utest_register_##SET##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #SET "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_##SET##_##NAME; \ + utest_state.tests[index].name = name; \ + utest_state.tests[index].index = 0; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##SET##_##NAME(int *utest_result) + +#define UTEST_F_SETUP(FIXTURE) \ + static void utest_f_setup_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F_TEARDOWN(FIXTURE) \ + static void utest_f_teardown_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F(FIXTURE, NAME) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_f_setup_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_f_teardown_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_run_##FIXTURE##_##NAME(int *, struct FIXTURE *); \ + static void utest_f_##FIXTURE##_##NAME(int *utest_result, \ + size_t utest_index) { \ + struct FIXTURE fixture; \ + (void)utest_index; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_f_setup_##FIXTURE(utest_result, &fixture); \ + if (UTEST_TEST_PASSED != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME(utest_result, &fixture); \ + utest_f_teardown_##FIXTURE(utest_result, &fixture); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ + utest_state.tests[index].name = name; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } else if (name) { \ + free(name); \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##FIXTURE##_##NAME(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_I_SETUP(FIXTURE) \ + static void utest_i_setup_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I_TEARDOWN(FIXTURE) \ + static void utest_i_teardown_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I(FIXTURE, NAME, INDEX) \ + UTEST_SURPRESS_WARNINGS_BEGIN \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##FIXTURE##_##NAME##_##INDEX(int *, struct FIXTURE *); \ + static void utest_i_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + size_t index) { \ + struct FIXTURE fixture; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_i_setup_##FIXTURE(utest_result, &fixture, index); \ + if (UTEST_TEST_PASSED != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME##_##INDEX(utest_result, &fixture); \ + utest_i_teardown_##FIXTURE(utest_result, &fixture, index); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME##_##INDEX) { \ + size_t i; \ + utest_uint64_t iUp; \ + for (i = 0; i < (INDEX); i++) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 32; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = UTEST_PTR_CAST( \ + struct utest_test_state_s *, \ + utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + if (utest_state.tests) { \ + utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ + utest_state.tests[index].index = i; \ + utest_state.tests[index].name = name; \ + iUp = UTEST_CAST(utest_uint64_t, i); \ + UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ + } else if (name) { \ + free(name); \ + } \ + } \ + } \ + UTEST_SURPRESS_WARNINGS_END \ + void utest_run_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +UTEST_WEAK +double utest_fabs(double d); +UTEST_WEAK +double utest_fabs(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.d; +} + +UTEST_WEAK +int utest_isnan(double d); +UTEST_WEAK +int utest_isnan(double d) { + union { + double d; + utest_uint64_t u; + } both; + both.d = d; + both.u &= 0x7fffffffffffffffu; + return both.u > 0x7ff0000000000000u; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +UTEST_WEAK +int utest_should_filter_test(const char *filter, const char *testcase); +UTEST_WEAK int utest_should_filter_test(const char *filter, + const char *testcase) { + if (filter) { + const char *filter_cur = filter; + const char *testcase_cur = testcase; + const char *filter_wildcard = UTEST_NULL; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* store the position of the wildcard */ + filter_wildcard = filter_cur; + + /* skip the wildcard character */ + filter_cur++; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* + we found another wildcard (filter is something like *foo*) so we + exit the current loop, and return to the parent loop to handle + the wildcard case + */ + break; + } else if (*filter_cur != *testcase_cur) { + /* otherwise our filter didn't match, so reset it */ + filter_cur = filter_wildcard; + } + + /* move testcase along */ + testcase_cur++; + + /* move filter along */ + filter_cur++; + } + + if (('\0' == *filter_cur) && ('\0' == *testcase_cur)) { + return 0; + } + + /* if the testcase has been exhausted, we don't have a match! */ + if ('\0' == *testcase_cur) { + return 1; + } + } else { + if (*testcase_cur != *filter_cur) { + /* test case doesn't match filter */ + return 1; + } else { + /* move our filter and testcase forward */ + testcase_cur++; + filter_cur++; + } + } + } + + if (('\0' != *filter_cur) || + (('\0' != *testcase_cur) && + ((filter == filter_cur) || ('*' != filter_cur[-1])))) { + /* we have a mismatch! */ + return 1; + } + } + + return 0; +} + +static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) { +#ifdef _MSC_VER + FILE *file; + if (0 == fopen_s(&file, filename, mode)) { + return file; + } else { + return UTEST_NULL; + } +#else + return fopen(filename, mode); +#endif +} + +static UTEST_INLINE int utest_main(int argc, const char *const argv[]); +int utest_main(int argc, const char *const argv[]) { + utest_uint64_t failed = 0; + utest_uint64_t skipped = 0; + size_t index = 0; + size_t *failed_testcases = UTEST_NULL; + size_t failed_testcases_length = 0; + size_t *skipped_testcases = UTEST_NULL; + size_t skipped_testcases_length = 0; + const char *filter = UTEST_NULL; + utest_uint64_t ran_tests = 0; + int enable_mixed_units = 0; + int random_order = 0; + utest_uint32_t seed = 0; + + enum colours { RESET, GREEN, RED, YELLOW }; + + const int use_colours = UTEST_COLOUR_OUTPUT(); + const char *colours[] = {"\033[0m", "\033[32m", "\033[31m", "\033[33m"}; + + if (!use_colours) { + for (index = 0; index < sizeof colours / sizeof colours[0]; index++) { + colours[index] = ""; + } + } + /* loop through all arguments looking for our options */ + for (index = 1; index < UTEST_CAST(size_t, argc); index++) { + /* Informational switches */ + const char help_str[] = "--help"; + const char list_str[] = "--list-tests"; + /* Test config switches */ + const char filter_str[] = "--filter="; + const char output_str[] = "--output="; + const char enable_mixed_units_str[] = "--enable-mixed-units"; + const char random_order_str[] = "--random-order"; + const char random_order_with_seed_str[] = "--random-order="; + + if (0 == UTEST_STRNCMP(argv[index], help_str, strlen(help_str))) { + printf("utest.h - the single file unit testing solution for C/C++!\n" + "Command line Options:\n" + " --help Show this message and exit.\n" + " --filter= Filter the test cases to run (EG. " + "MyTest*.a would run MyTestCase.a but not MyTestCase.b).\n" + " --list-tests List testnames, one per line. Output " + "names can be passed to --filter.\n"); + printf(" --output= Output an xunit XML file to the file " + "specified in .\n" + " --enable-mixed-units Enable the per-test output to contain " + "mixed units (s/ms/us/ns).\n" + " --random-order[=] Randomize the order that the tests are " + "ran in. If the optional argument is not provided, then a " + "random starting seed is used.\n"); + goto cleanup; + } else if (0 == + UTEST_STRNCMP(argv[index], filter_str, strlen(filter_str))) { + /* user wants to filter what test cases run! */ + filter = argv[index] + strlen(filter_str); + } else if (0 == + UTEST_STRNCMP(argv[index], output_str, strlen(output_str))) { + utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+"); + } else if (0 == UTEST_STRNCMP(argv[index], list_str, strlen(list_str))) { + for (index = 0; index < utest_state.tests_length; index++) { + UTEST_PRINTF("%s\n", utest_state.tests[index].name); + } + /* when printing the test list, don't actually run the tests */ + return 0; + } else if (0 == UTEST_STRNCMP(argv[index], enable_mixed_units_str, + strlen(enable_mixed_units_str))) { + enable_mixed_units = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_with_seed_str, + strlen(random_order_with_seed_str))) { + seed = + UTEST_CAST(utest_uint32_t, + strtoul(argv[index] + strlen(random_order_with_seed_str), + UTEST_NULL, 10)); + random_order = 1; + } else if (0 == UTEST_STRNCMP(argv[index], random_order_str, + strlen(random_order_str))) { + const utest_int64_t ns = utest_ns(); + + // Some really poor pseudo-random using the current time. I do this + // because I really want to avoid using C's rand() because that'd mean our + // random would be affected by any srand() usage by the user (which I + // don't want). + seed = UTEST_CAST(utest_uint32_t, ns >> 32) * 31 + + UTEST_CAST(utest_uint32_t, ns & 0xffffffff); + random_order = 1; + } + } + + if (random_order) { + // Use Fisher-Yates with the Durstenfield's version to randomly re-order the + // tests. + for (index = utest_state.tests_length; index > 1; index--) { + // For the random order we'll use PCG. + const utest_uint32_t state = seed; + const utest_uint32_t word = + ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + const utest_uint32_t next = + ((word >> 22u) ^ word) % UTEST_CAST(utest_uint32_t, index); + + // Swap the randomly chosen element into the last location. + const struct utest_test_state_s copy = utest_state.tests[index - 1]; + utest_state.tests[index - 1] = utest_state.tests[next]; + utest_state.tests[next] = copy; + + // Move the seed onwards. + seed = seed * 747796405u + 2891336453u; + } + } + + for (index = 0; index < utest_state.tests_length; index++) { + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + ran_tests++; + } + + printf("%s[==========]%s Running %" UTEST_PRIu64 " test cases.\n", + colours[GREEN], colours[RESET], UTEST_CAST(utest_uint64_t, ran_tests)); + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + fprintf(utest_state.output, + "\n", + UTEST_CAST(utest_uint64_t, ran_tests)); + fprintf(utest_state.output, + "\n", + UTEST_CAST(utest_uint64_t, ran_tests)); + } + + for (index = 0; index < utest_state.tests_length; index++) { + int result = UTEST_TEST_PASSED; + utest_int64_t ns = 0; + + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + printf("%s[ RUN ]%s %s\n", colours[GREEN], colours[RESET], + utest_state.tests[index].name); + + if (utest_state.output) { + fprintf(utest_state.output, "", + utest_state.tests[index].name); + } + + ns = utest_ns(); + errno = 0; +#if defined(UTEST_HAS_EXCEPTIONS) + UTEST_SURPRESS_WARNING_BEGIN + try { + utest_state.tests[index].func(&result, utest_state.tests[index].index); + } catch (const std::exception &err) { + printf(" Exception : %s\n", err.what()); + result = UTEST_TEST_FAILURE; + } catch (...) { + printf(" Exception : Unknown\n"); + result = UTEST_TEST_FAILURE; + } + UTEST_SURPRESS_WARNING_END +#else + utest_state.tests[index].func(&result, utest_state.tests[index].index); +#endif + ns = utest_ns() - ns; + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + } + + // Record the failing test. + if (UTEST_TEST_FAILURE == result) { + const size_t failed_testcase_index = failed_testcases_length++; + failed_testcases = UTEST_PTR_CAST( + size_t *, utest_realloc(UTEST_PTR_CAST(void *, failed_testcases), + sizeof(size_t) * failed_testcases_length)); + if (UTEST_NULL != failed_testcases) { + failed_testcases[failed_testcase_index] = index; + } + failed++; + } else if (UTEST_TEST_SKIPPED == result) { + const size_t skipped_testcase_index = skipped_testcases_length++; + skipped_testcases = UTEST_PTR_CAST( + size_t *, utest_realloc(UTEST_PTR_CAST(void *, skipped_testcases), + sizeof(size_t) * skipped_testcases_length)); + if (UTEST_NULL != skipped_testcases) { + skipped_testcases[skipped_testcase_index] = index; + } + skipped++; + } + + { + const char *const units[] = {"ns", "us", "ms", "s", UTEST_NULL}; + unsigned int unit_index = 0; + utest_int64_t time = ns; + + if (enable_mixed_units) { + for (unit_index = 0; UTEST_NULL != units[unit_index]; unit_index++) { + if (10000 > time) { + break; + } + + time /= 1000; + } + } + + if (UTEST_TEST_FAILURE == result) { + printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[RED], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else if (UTEST_TEST_SKIPPED == result) { + printf("%s[ SKIPPED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[YELLOW], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } else { + printf("%s[ OK ]%s %s (%" UTEST_PRId64 "%s)\n", colours[GREEN], + colours[RESET], utest_state.tests[index].name, time, + units[unit_index]); + } + } + } + + printf("%s[==========]%s %" UTEST_PRIu64 " test cases ran.\n", colours[GREEN], + colours[RESET], ran_tests); + printf("%s[ PASSED ]%s %" UTEST_PRIu64 " tests.\n", colours[GREEN], + colours[RESET], ran_tests - failed - skipped); + + if (0 != skipped) { + printf("%s[ SKIPPED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[YELLOW], colours[RESET], skipped); + for (index = 0; index < skipped_testcases_length; index++) { + printf("%s[ SKIPPED ]%s %s\n", colours[YELLOW], colours[RESET], + utest_state.tests[skipped_testcases[index]].name); + } + } + + if (0 != failed) { + printf("%s[ FAILED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[RED], colours[RESET], failed); + for (index = 0; index < failed_testcases_length; index++) { + printf("%s[ FAILED ]%s %s\n", colours[RED], colours[RESET], + utest_state.tests[failed_testcases[index]].name); + } + } + + if (utest_state.output) { + fprintf(utest_state.output, "\n\n"); + } + +cleanup: + for (index = 0; index < utest_state.tests_length; index++) { + free(UTEST_PTR_CAST(void *, utest_state.tests[index].name)); + } + + free(UTEST_PTR_CAST(void *, skipped_testcases)); + free(UTEST_PTR_CAST(void *, failed_testcases)); + free(UTEST_PTR_CAST(void *, utest_state.tests)); + + if (utest_state.output) { + fclose(utest_state.output); + } + + return UTEST_CAST(int, failed); +} + +#if defined(__clang__) +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic pop +#endif +#endif + +/* + we need, in exactly one source file, define the global struct that will hold + the data we need to run utest. This macro allows the user to declare the + data without having to use the UTEST_MAIN macro, thus allowing them to write + their own main() function. +*/ +#define UTEST_STATE() struct utest_state_s utest_state = {0, 0, 0} + +/* + define a main() function to call into utest.h and start executing tests! A + user can optionally not use this macro, and instead define their own main() + function and manually call utest_main. The user must, in exactly one source + file, use the UTEST_STATE macro to declare a global struct variable that + utest requires. +*/ +#define UTEST_MAIN() \ + UTEST_STATE(); \ + int main(int argc, const char *const argv[]) { \ + return utest_main(argc, argv); \ + } + +#endif /* SHEREDOM_UTEST_H_INCLUDED */ diff --git a/src/utf8/test/utfmain.exe b/src/utf8/test/utfmain.exe new file mode 100644 index 0000000..ba54a22 Binary files /dev/null and b/src/utf8/test/utfmain.exe differ diff --git "a/src/utf8/utf8 - c\303\262pia.h" "b/src/utf8/utf8 - c\303\262pia.h" new file mode 100644 index 0000000..5652fe2 --- /dev/null +++ "b/src/utf8/utf8 - c\303\262pia.h" @@ -0,0 +1,1693 @@ +/* The latest version of this library is available on GitHub; + * https://github.com/sheredom/utf8.h */ + +/* This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to */ + +#ifndef SHEREDOM_UTF8_H_INCLUDED +#define SHEREDOM_UTF8_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: bytes padding added after construct */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +typedef __int32 utf8_int32_t; +#else +#include +typedef int32_t utf8_int32_t; +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) +#define utf8_nonnull +#define utf8_pure +#define utf8_restrict __restrict +#define utf8_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define utf8_nonnull __attribute__((nonnull)) +#define utf8_pure __attribute__((pure)) +#define utf8_restrict __restrict__ +#define utf8_weak __attribute__((weak)) +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __cplusplus +#define utf8_null NULL +#else +#define utf8_null 0 +#endif + +#if (defined(__cplusplus) && __cplusplus >= 201402L) +#define utf8_constexpr14 constexpr +#define utf8_constexpr14_impl constexpr +#else +/* constexpr and weak are incompatible. so only enable one of them */ +#define utf8_constexpr14 utf8_weak +#define utf8_constexpr14_impl +#endif + +#if defined(__cplusplus) && __cplusplus >= 202002L +using utf8_int8_t = char8_t; /* Introduced in C++20 */ +#else +typedef char utf8_int8_t; +#endif + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8casecmp(const utf8_int8_t *src1, const utf8_int8_t *src2); + +/* Append the utf8 string src onto the utf8 string dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Find the first match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8chr(const utf8_int8_t *src, utf8_int32_t chr); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. */ +utf8_constexpr14 utf8_nonnull utf8_pure int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +/* Copy the utf8 string src onto the memory allocated in dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints not from the utf8 string reject. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8cspn(const utf8_int8_t *src, const utf8_int8_t *reject); + +/* Duplicate the utf8 string src by getting its size, malloc'ing a new buffer + * copying over the data, and returning that. Or 0 if malloc failed. */ +utf8_weak utf8_int8_t *utf8dup(const utf8_int8_t *src); + +/* Number of utf8 codepoints in the utf8 string str, + * excluding the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8len(const utf8_int8_t *str); + +/* Similar to utf8len, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8nlen(const utf8_int8_t *str, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. Checking at most n bytes of each utf8 + * string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncasecmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Append the utf8 string src onto the utf8 string dst, + * writing at most n+1 bytes. Can produce an invalid utf8 + * string if n falls partway through a utf8 codepoint. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. Checking at most n + * bytes of each utf8 string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Copy the utf8 string src onto the memory allocated in dst. + * Copies at most n bytes. If n falls partway through a utf8 + * codepoint, or if dst doesn't have enough room for a null + * terminator, the final string will be cut short to preserve + * utf8 validity. */ + +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise */ +utf8_weak utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n); + +/* Locates the first occurrence in the utf8 string str of any byte in the + * utf8 string accept, or 0 if no match was found. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8pbrk(const utf8_int8_t *str, const utf8_int8_t *accept); + +/* Find the last match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8rchr(const utf8_int8_t *src, int chr); + +/* Number of bytes in the utf8 string str, + * including the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8size(const utf8_int8_t *str); + +/* Similar to utf8size, except that the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8size_lazy(const utf8_int8_t *str); + +/* Similar to utf8size, except that only at most n bytes of src are looked and + * the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8nsize_lazy(const utf8_int8_t *str, size_t n); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints from the utf8 string accept. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8spn(const utf8_int8_t *src, const utf8_int8_t *accept); + +/* The position of the utf8 string needle in the utf8 string haystack. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8str(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* The position of the utf8 string needle in the utf8 string haystack, case + * insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8casestr(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* Return 0 on success, or the position of the invalid + * utf8 codepoint on failure. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8valid(const utf8_int8_t *str); + +/* Similar to utf8valid, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8nvalid(const utf8_int8_t *str, size_t n); + +/* Given a null-terminated string, makes the string valid by replacing invalid + * codepoints with a 1-byte replacement. Returns 0 on success. */ +utf8_nonnull utf8_weak int utf8makevalid(utf8_int8_t *str, + const utf8_int32_t replacement); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the next utf8 codepoint after the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Calculates the size of the next utf8 codepoint in str. */ +utf8_constexpr14 utf8_nonnull size_t +utf8codepointcalcsize(const utf8_int8_t *str); + +/* Returns the size of the given codepoint in bytes. */ +utf8_constexpr14 size_t utf8codepointsize(utf8_int32_t chr); + +/* Write a codepoint to the given string, and return the address to the next + * place after the written codepoint. Pass how many bytes left in the buffer to + * n. If there is not enough space for the codepoint, this function returns + * null. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n); + +/* Returns 1 if the given character is lowercase, or 0 if it is not. */ +utf8_constexpr14 int utf8islower(utf8_int32_t chr); + +/* Returns 1 if the given character is uppercase, or 0 if it is not. */ +utf8_constexpr14 int utf8isupper(utf8_int32_t chr); + +/* Transform the given string into all lowercase codepoints. */ +utf8_nonnull utf8_weak void utf8lwr(utf8_int8_t *utf8_restrict str); + +/* Transform the given string into all uppercase codepoints. */ +utf8_nonnull utf8_weak void utf8upr(utf8_int8_t *utf8_restrict str); + +/* Make a codepoint lower case if possible. */ +utf8_constexpr14 utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); + +/* Make a codepoint upper case if possible. */ +utf8_constexpr14 utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the previous utf8 codepoint before the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Duplicate the utf8 string src by getting its size, calling alloc_func_ptr to + * copy over data to a new buffer, and returning that. Or 0 if alloc_func_ptr + * returned null. */ +utf8_weak utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise. */ +utf8_weak utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +#undef utf8_weak +#undef utf8_pure +#undef utf8_nonnull + +utf8_constexpr14_impl int utf8casecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + for (;;) { + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + + /* lower the srcs if required */ + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + /* lower the srcs if required */ + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } +} + +utf8_int8_t *utf8cat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl utf8_int8_t *utf8chr(const utf8_int8_t *src, + utf8_int32_t chr) { + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've made c into a 2 utf8 codepoint string, one for the chr we are + * seeking, another for the null terminating byte. Now use utf8str to + * search */ + return utf8str(src, c); +} + +utf8_constexpr14_impl int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + while (('\0' != *src1) || ('\0' != *src2)) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_constexpr14_impl int utf8coll(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +utf8_int8_t *utf8cpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* append null terminating byte */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl size_t utf8cspn(const utf8_int8_t *src, + const utf8_int8_t *reject) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *r = reject; + size_t offset = 0; + + while ('\0' != *r) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *r)) && (0 < offset)) { + return chars; + } else { + if (*r == src[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + r++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + r++; + } while (0x80 == (0xc0 & *r)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *r, so didn't get a chance to test it */ + if (0 < offset) { + return chars; + } + + /* the current utf8 codepoint in src did not match reject, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + src++; + } while ((0x80 == (0xc0 & *src))); + chars++; + } + + return chars; +} + +utf8_int8_t *utf8dup(const utf8_int8_t *src) { + return utf8dup_ex(src, utf8_null, utf8_null); +} + +utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *n = utf8_null; + + /* figure out how many bytes (including the terminator) we need to copy first + */ + size_t bytes = utf8size(src); + + if (alloc_func_ptr) { + n = alloc_func_ptr(user_data, bytes); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + n = (utf8_int8_t *)malloc(bytes); +#else + return utf8_null; +#endif + } + + if (utf8_null == n) { + /* out of memory so we bail */ + return utf8_null; + } else { + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes]) { + n[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + n[bytes] = '\0'; + return n; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8fry(const utf8_int8_t *str); + +utf8_constexpr14_impl size_t utf8len(const utf8_int8_t *str) { + return utf8nlen(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nlen(const utf8_int8_t *str, size_t n) { + const utf8_int8_t *t = str; + size_t length = 0; + + while ((size_t)(str - t) < n && '\0' != *str) { + if (0xf0 == (0xf8 & *str)) { + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else { /* if (0x00 == (0x80 & *s)) { */ + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } + + /* no matter the bytes we marched s forward by, it was + * only 1 utf8 codepoint */ + length++; + } + + if ((size_t)(str - t) > n) { + length--; + } + return length; +} + +utf8_constexpr14_impl int utf8ncasecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + do { + const utf8_int8_t *const s1 = src1; + const utf8_int8_t *const s2 = src2; + + /* first check that we have enough bytes left in n to contain an entire + * codepoint */ + if (0 == n) { + return 0; + } + + if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) { + const utf8_int32_t c1 = (0xe0 & *s1); + const utf8_int32_t c2 = (0xe0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) { + const utf8_int32_t c1 = (0xf0 & *s1); + const utf8_int32_t c2 = (0xf0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) { + const utf8_int32_t c1 = (0xf8 & *s1); + const utf8_int32_t c2 = (0xf8 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + n -= utf8codepointsize(src1_orig_cp); + + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } while (0 < n); + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte + * stopping if we run out of space */ + while (('\0' != *src) && (0 != n--)) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl int utf8ncmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + while ((0 != n--) && (('\0' != *src1) || ('\0' != *src2))) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + size_t index = 0, check_index = 0; + + if (n == 0) { + return dst; + } + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + for (index = 0; index < n; index++) { + d[index] = src[index]; + if ('\0' == src[index]) { + break; + } + } + + for (check_index = index - 1; + check_index > 0 && 0x80 == (0xc0 & d[check_index]); check_index--) { + /* just moving the index */ + } + + if (check_index < index && + ((index - check_index) < utf8codepointcalcsize(&d[check_index]) || + (index - check_index) == n)) { + index = check_index; + } + + /* append null terminating byte */ + for (; index < n; index++) { + d[index] = 0; + } + + return dst; +} + +utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n) { + return utf8ndup_ex(src, n, utf8_null, utf8_null); +} + +utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *c = utf8_null; + size_t bytes = 0; + + /* Find the end of the string or stop when n is reached */ + while ('\0' != src[bytes] && bytes < n) { + bytes++; + } + + /* In case bytes is actually less than n, we need to set it + * to be used later in the copy byte by byte. */ + n = bytes; + + if (alloc_func_ptr) { + c = alloc_func_ptr(user_data, bytes + 1); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + c = (utf8_int8_t *)malloc(bytes + 1); +#else + c = utf8_null; +#endif + } + + if (utf8_null == c) { + /* out of memory so we bail */ + return utf8_null; + } + + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes] && bytes < n) { + c[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + c[bytes] = '\0'; + return c; +} + +utf8_constexpr14_impl utf8_int8_t *utf8rchr(const utf8_int8_t *src, int chr) { + + utf8_int8_t *match = utf8_null; + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((int)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((int)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((int)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've created a 2 utf8 codepoint string in c that is + * the utf8 character asked for by chr, and a null + * terminating byte */ + + while ('\0' != *src) { + size_t offset = 0; + + while ((src[offset] == c[offset]) && ('\0' != src[offset])) { + offset++; + } + + if ('\0' == c[offset]) { + /* we found a matching utf8 code point */ + match = (utf8_int8_t *)src; + src += offset; + + if ('\0' == *src) { + break; + } + } else { + src += offset; + + /* need to march s along to next utf8 codepoint start + * (the next byte that doesn't match 0b10xxxxxx) */ + if ('\0' != *src) { + do { + src++; + } while (0x80 == (0xc0 & *src)); + } + } + } + + /* return the last match we found (or 0 if no match was found) */ + return match; +} + +utf8_constexpr14_impl utf8_int8_t *utf8pbrk(const utf8_int8_t *str, + const utf8_int8_t *accept) { + while ('\0' != *str) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *a is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + return (utf8_int8_t *)str; + } else { + if (*a == str[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + a++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* we found a match on the last utf8 codepoint */ + if (0 < offset) { + return (utf8_int8_t *)str; + } + + /* the current utf8 codepoint in src did not match accept, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + str++; + } while ((0x80 == (0xc0 & *str))); + } + + return utf8_null; +} + +utf8_constexpr14_impl size_t utf8size(const utf8_int8_t *str) { + return utf8size_lazy(str) + 1; +} + +utf8_constexpr14_impl size_t utf8size_lazy(const utf8_int8_t *str) { + return utf8nsize_lazy(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nsize_lazy(const utf8_int8_t *str, size_t n) { + size_t size = 0; + while (size < n && '\0' != str[size]) { + size++; + } + return size; +} + +utf8_constexpr14_impl size_t utf8spn(const utf8_int8_t *src, + const utf8_int8_t *accept) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + /* found a match, so increment the number of utf8 codepoints + * that have matched and stop checking whether any other utf8 + * codepoints in a match */ + chars++; + src += offset; + offset = 0; + break; + } else { + if (*a == src[offset]) { + offset++; + a++; + } else { + /* a could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning, */ + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *a, so didn't get a chance to test it */ + if (0 < offset) { + chars++; + src += offset; + continue; + } + + /* if a got to its terminating null byte, then we didn't find a match. + * Return the current number of matched utf8 codepoints */ + if ('\0' == *a) { + return chars; + } + } + + return chars; +} + +utf8_constexpr14_impl utf8_int8_t *utf8str(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + utf8_int32_t throwaway_codepoint = 0; + + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + while ('\0' != *haystack) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + + while (*haystack == *n && (*haystack != '\0' && *n != '\0')) { + n++; + haystack++; + } + + if ('\0' == *n) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } else { + /* h could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning + * starting from the current character */ + haystack = utf8codepoint(maybeMatch, &throwaway_codepoint); + } + } + + /* no match */ + return utf8_null; +} + +utf8_constexpr14_impl utf8_int8_t *utf8casestr(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + for (;;) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + utf8_int32_t h_cp = 0, n_cp = 0; + + /* Get the next code point and track it */ + const utf8_int8_t *nextH = haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + + while ((0 != h_cp) && (0 != n_cp)) { + h_cp = utf8lwrcodepoint(h_cp); + n_cp = utf8lwrcodepoint(n_cp); + + /* if we find a mismatch, bail out! */ + if (h_cp != n_cp) { + break; + } + + haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + } + + if (0 == n_cp) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } + + if (0 == h_cp) { + /* no match */ + return utf8_null; + } + + /* Roll back to the next code point in the haystack to test */ + haystack = nextH; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8valid(const utf8_int8_t *str) { + return utf8nvalid(str, SIZE_MAX); +} + +utf8_constexpr14_impl utf8_int8_t *utf8nvalid(const utf8_int8_t *str, + size_t n) { + const utf8_int8_t *t = str; + size_t consumed = 0; + + while ((void)(consumed = (size_t)(str - t)), consumed < n && '\0' != *str) { + const size_t remaining = n - consumed; + + if (0xf0 == (0xf8 & *str)) { + /* ensure that there's 4 bytes or more remaining */ + if (remaining < 4) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2])) || + (0x80 != (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 4 bytes */ + if ((remaining != 4) && (0x80 == (0xc0 & str[4]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 4-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x07 & str[0])) && (0 == (0x30 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* ensure that there's 3 bytes or more remaining */ + if (remaining < 3) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 3 bytes */ + if ((remaining != 3) && (0x80 == (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 3-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x0f & str[0])) && (0 == (0x20 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* ensure that there's 2 bytes or more remaining */ + if (remaining < 2) { + return (utf8_int8_t *)str; + } + + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & str[1])) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 2 bytes */ + if ((remaining != 2) && (0x80 == (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 4 bits of this 2-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if (0 == (0x1e & str[0])) { + return (utf8_int8_t *)str; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else if (0x00 == (0x80 & *str)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } else { + /* we have an invalid 0b1xxxxxxx utf8 code point entry */ + return (utf8_int8_t *)str; + } + } + + return utf8_null; +} + +int utf8makevalid(utf8_int8_t *str, const utf8_int32_t replacement) { + utf8_int8_t *read = str; + utf8_int8_t *write = read; + const utf8_int8_t r = (utf8_int8_t)replacement; + utf8_int32_t codepoint = 0; + + if (replacement > 0x7f) { + return -1; + } + + while ('\0' != *read) { + if (0xf0 == (0xf8 & *read)) { + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2])) || + (0x80 != (0xc0 & read[3]))) { + *write++ = r; + read++; + continue; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 4); + } else if (0xe0 == (0xf0 & *read)) { + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2]))) { + *write++ = r; + read++; + continue; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 3); + } else if (0xc0 == (0xe0 & *read)) { + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & read[1])) { + *write++ = r; + read++; + continue; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 2); + } else if (0x00 == (0x80 & *read)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 1); + } else { + /* if we got here then we've got a dangling continuation (0b10xxxxxx) */ + *write++ = r; + read++; + continue; + } + } + + *write = '\0'; + + return 0; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | + ((0x3f & str[2]) << 6) | (0x3f & str[3]); + str += 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]); + str += 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]); + str += 2; + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = str[0]; + str += 1; + } + + return (utf8_int8_t *)str; +} + +utf8_constexpr14_impl size_t utf8codepointcalcsize(const utf8_int8_t *str) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + return 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + return 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + return 2; + } + + /* 1 byte utf8 codepoint otherwise */ + return 1; +} + +utf8_constexpr14_impl size_t utf8codepointsize(utf8_int32_t chr) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + return 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + return 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + return 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + return 4; + } +} + +utf8_int8_t *utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + if (n < 1) { + return utf8_null; + } + str[0] = (utf8_int8_t)chr; + str += 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + if (n < 2) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)((chr >> 6) & 0x1f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 3) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)((chr >> 12) & 0x0f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 4) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)((chr >> 18) & 0x07)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 4; + } + + return str; +} + +utf8_constexpr14_impl int utf8islower(utf8_int32_t chr) { + return chr != utf8uprcodepoint(chr); +} + +utf8_constexpr14_impl int utf8isupper(utf8_int32_t chr) { + return chr != utf8lwrcodepoint(chr); +} + +void utf8lwr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +void utf8upr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8uprcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +utf8_constexpr14_impl utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) { + if (((0x0041 <= cp) && (0x005a >= cp)) || + ((0x00c0 <= cp) && (0x00d6 >= cp)) || + ((0x00d8 <= cp) && (0x00de >= cp)) || + ((0x0391 <= cp) && (0x03a1 >= cp)) || + ((0x03a3 <= cp) && (0x03ab >= cp)) || + ((0x0410 <= cp) && (0x042f >= cp))) { + cp += 32; + } else if ((0x0400 <= cp) && (0x040f >= cp)) { + cp += 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp |= 0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp += 1; + cp &= ~0x1; + } else { + switch (cp) { + default: + break; + case 0x0178: + cp = 0x00ff; + break; + case 0x0243: + cp = 0x0180; + break; + case 0x018e: + cp = 0x01dd; + break; + case 0x023d: + cp = 0x019a; + break; + case 0x0220: + cp = 0x019e; + break; + case 0x01b7: + cp = 0x0292; + break; + case 0x01c4: + cp = 0x01c6; + break; + case 0x01c7: + cp = 0x01c9; + break; + case 0x01ca: + cp = 0x01cc; + break; + case 0x01f1: + cp = 0x01f3; + break; + case 0x01f7: + cp = 0x01bf; + break; + case 0x0187: + cp = 0x0188; + break; + case 0x018b: + cp = 0x018c; + break; + case 0x0191: + cp = 0x0192; + break; + case 0x0198: + cp = 0x0199; + break; + case 0x01a7: + cp = 0x01a8; + break; + case 0x01ac: + cp = 0x01ad; + break; + case 0x01b8: + cp = 0x01b9; + break; + case 0x01bc: + cp = 0x01bd; + break; + case 0x01f4: + cp = 0x01f5; + break; + case 0x023b: + cp = 0x023c; + break; + case 0x0241: + cp = 0x0242; + break; + case 0x03fd: + cp = 0x037b; + break; + case 0x03fe: + cp = 0x037c; + break; + case 0x03ff: + cp = 0x037d; + break; + case 0x037f: + cp = 0x03f3; + break; + case 0x0386: + cp = 0x03ac; + break; + case 0x0388: + cp = 0x03ad; + break; + case 0x0389: + cp = 0x03ae; + break; + case 0x038a: + cp = 0x03af; + break; + case 0x038c: + cp = 0x03cc; + break; + case 0x038e: + cp = 0x03cd; + break; + case 0x038f: + cp = 0x03ce; + break; + case 0x0370: + cp = 0x0371; + break; + case 0x0372: + cp = 0x0373; + break; + case 0x0376: + cp = 0x0377; + break; + case 0x03f4: + cp = 0x03b8; + break; + case 0x03cf: + cp = 0x03d7; + break; + case 0x03f9: + cp = 0x03f2; + break; + case 0x03f7: + cp = 0x03f8; + break; + case 0x03fa: + cp = 0x03fb; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { + if (((0x0061 <= cp) && (0x007a >= cp)) || + ((0x00e0 <= cp) && (0x00f6 >= cp)) || + ((0x00f8 <= cp) && (0x00fe >= cp)) || + ((0x03b1 <= cp) && (0x03c1 >= cp)) || + ((0x03c3 <= cp) && (0x03cb >= cp)) || + ((0x0430 <= cp) && (0x044f >= cp))) { + cp -= 32; + } else if ((0x0450 <= cp) && (0x045f >= cp)) { + cp -= 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp &= ~0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp -= 1; + cp |= 0x1; + } else { + switch (cp) { + default: + break; + case 0x00ff: + cp = 0x0178; + break; + case 0x0180: + cp = 0x0243; + break; + case 0x01dd: + cp = 0x018e; + break; + case 0x019a: + cp = 0x023d; + break; + case 0x019e: + cp = 0x0220; + break; + case 0x0292: + cp = 0x01b7; + break; + case 0x01c6: + cp = 0x01c4; + break; + case 0x01c9: + cp = 0x01c7; + break; + case 0x01cc: + cp = 0x01ca; + break; + case 0x01f3: + cp = 0x01f1; + break; + case 0x01bf: + cp = 0x01f7; + break; + case 0x0188: + cp = 0x0187; + break; + case 0x018c: + cp = 0x018b; + break; + case 0x0192: + cp = 0x0191; + break; + case 0x0199: + cp = 0x0198; + break; + case 0x01a8: + cp = 0x01a7; + break; + case 0x01ad: + cp = 0x01ac; + break; + case 0x01b9: + cp = 0x01b8; + break; + case 0x01bd: + cp = 0x01bc; + break; + case 0x01f5: + cp = 0x01f4; + break; + case 0x023c: + cp = 0x023b; + break; + case 0x0242: + cp = 0x0241; + break; + case 0x037b: + cp = 0x03fd; + break; + case 0x037c: + cp = 0x03fe; + break; + case 0x037d: + cp = 0x03ff; + break; + case 0x03f3: + cp = 0x037f; + break; + case 0x03ac: + cp = 0x0386; + break; + case 0x03ad: + cp = 0x0388; + break; + case 0x03ae: + cp = 0x0389; + break; + case 0x03af: + cp = 0x038a; + break; + case 0x03cc: + cp = 0x038c; + break; + case 0x03cd: + cp = 0x038e; + break; + case 0x03ce: + cp = 0x038f; + break; + case 0x0371: + cp = 0x0370; + break; + case 0x0373: + cp = 0x0372; + break; + case 0x0377: + cp = 0x0376; + break; + case 0x03d1: + cp = 0x0398; + break; + case 0x03d7: + cp = 0x03cf; + break; + case 0x03f2: + cp = 0x03f9; + break; + case 0x03f8: + cp = 0x03f7; + break; + case 0x03fb: + cp = 0x03fa; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + const utf8_int8_t *s = (const utf8_int8_t *)str; + + if (0xf0 == (0xf8 & s[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | + ((0x3f & s[2]) << 6) | (0x3f & s[3]); + } else if (0xe0 == (0xf0 & s[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); + } else if (0xc0 == (0xe0 & s[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = s[0]; + } + + do { + s--; + } while ((0 != (0x80 & s[0])) && (0x80 == (0xc0 & s[0]))); + + return (utf8_int8_t *)s; +} + +#undef utf8_restrict +#undef utf8_constexpr14 +#undef utf8_null + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif /* SHEREDOM_UTF8_H_INCLUDED */ diff --git a/src/utf8/utf8.h b/src/utf8/utf8.h new file mode 100644 index 0000000..c83f812 --- /dev/null +++ b/src/utf8/utf8.h @@ -0,0 +1,1685 @@ +/* The latest version of this library is available on GitHub; + * https://github.com/sheredom/utf8.h */ + +/* This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to */ + +#ifndef SHEREDOM_UTF8_H_INCLUDED +#define SHEREDOM_UTF8_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: bytes padding added after construct */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +typedef __int32 utf8_int32_t; +#else +#include +typedef int32_t utf8_int32_t; +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" + +#if __has_warning("-Wunsafe-buffer-usage") +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#define utf8_nonnull +#define utf8_pure +#define utf8_restrict __restrict +#define utf8_weak __inline + +#ifdef __cplusplus +#define utf8_null NULL +#else +#define utf8_null 0 +#endif + +#if (defined(__cplusplus) && __cplusplus >= 201402L) +#define utf8_constexpr14 constexpr +#define utf8_constexpr14_impl constexpr +#else +/* constexpr and weak are incompatible. so only enable one of them */ +#define utf8_constexpr14 utf8_weak +#define utf8_constexpr14_impl +#endif + +#if defined(__cplusplus) && __cplusplus >= 202002L +using utf8_int8_t = char8_t; /* Introduced in C++20 */ +#else +typedef char utf8_int8_t; +#endif + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8casecmp(const utf8_int8_t *src1, const utf8_int8_t *src2); + +/* Append the utf8 string src onto the utf8 string dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Find the first match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8chr(const utf8_int8_t *src, utf8_int32_t chr); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. */ +utf8_constexpr14 utf8_nonnull utf8_pure int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +/* Copy the utf8 string src onto the memory allocated in dst. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8cpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints not from the utf8 string reject. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8cspn(const utf8_int8_t *src, const utf8_int8_t *reject); + +/* Duplicate the utf8 string src by getting its size, malloc'ing a new buffer + * copying over the data, and returning that. Or 0 if malloc failed. */ +utf8_weak utf8_int8_t *utf8dup(const utf8_int8_t *src); + +/* Number of utf8 codepoints in the utf8 string str, + * excluding the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8len(const utf8_int8_t *str); + +/* Similar to utf8len, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8nlen(const utf8_int8_t *str, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 > + * src2 respectively, case insensitive. Checking at most n bytes of each utf8 + * string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncasecmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Append the utf8 string src onto the utf8 string dst, + * writing at most n+1 bytes. Can produce an invalid utf8 + * string if n falls partway through a utf8 codepoint. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncat(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Return less than 0, 0, greater than 0 if src1 < src2, + * src1 == src2, src1 > src2 respectively. Checking at most n + * bytes of each utf8 string. */ +utf8_constexpr14 utf8_nonnull utf8_pure int +utf8ncmp(const utf8_int8_t *src1, const utf8_int8_t *src2, size_t n); + +/* Copy the utf8 string src onto the memory allocated in dst. + * Copies at most n bytes. If n falls partway through a utf8 + * codepoint, or if dst doesn't have enough room for a null + * terminator, the final string will be cut short to preserve + * utf8 validity. */ + +utf8_nonnull utf8_weak utf8_int8_t * +utf8ncpy(utf8_int8_t *utf8_restrict dst, const utf8_int8_t *utf8_restrict src, + size_t n); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise */ +utf8_weak utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n); + +/* Locates the first occurrence in the utf8 string str of any byte in the + * utf8 string accept, or 0 if no match was found. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8pbrk(const utf8_int8_t *str, const utf8_int8_t *accept); + +/* Find the last match of the utf8 codepoint chr in the utf8 string src. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8rchr(const utf8_int8_t *src, int chr); + +/* Number of bytes in the utf8 string str, + * including the null terminating byte. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t utf8size(const utf8_int8_t *str); + +/* Similar to utf8size, except that the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8size_lazy(const utf8_int8_t *str); + +/* Similar to utf8size, except that only at most n bytes of src are looked and + * the null terminating byte is excluded. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8nsize_lazy(const utf8_int8_t *str, size_t n); + +/* Number of utf8 codepoints in the utf8 string src that consists entirely + * of utf8 codepoints from the utf8 string accept. */ +utf8_constexpr14 utf8_nonnull utf8_pure size_t +utf8spn(const utf8_int8_t *src, const utf8_int8_t *accept); + +/* The position of the utf8 string needle in the utf8 string haystack. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8str(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* The position of the utf8 string needle in the utf8 string haystack, case + * insensitive. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8casestr(const utf8_int8_t *haystack, const utf8_int8_t *needle); + +/* Return 0 on success, or the position of the invalid + * utf8 codepoint on failure. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8valid(const utf8_int8_t *str); + +/* Similar to utf8valid, except that only at most n bytes of src are looked. */ +utf8_constexpr14 utf8_nonnull utf8_pure utf8_int8_t * +utf8nvalid(const utf8_int8_t *str, size_t n); + +/* Given a null-terminated string, makes the string valid by replacing invalid + * codepoints with a 1-byte replacement. Returns 0 on success. */ +utf8_nonnull utf8_weak int utf8makevalid(utf8_int8_t *str, + const utf8_int32_t replacement); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the next utf8 codepoint after the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Calculates the size of the next utf8 codepoint in str. */ +utf8_constexpr14 utf8_nonnull size_t +utf8codepointcalcsize(const utf8_int8_t *str); + +/* Returns the size of the given codepoint in bytes. */ +utf8_constexpr14 size_t utf8codepointsize(utf8_int32_t chr); + +/* Write a codepoint to the given string, and return the address to the next + * place after the written codepoint. Pass how many bytes left in the buffer to + * n. If there is not enough space for the codepoint, this function returns + * null. */ +utf8_nonnull utf8_weak utf8_int8_t * +utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n); + +/* Returns 1 if the given character is lowercase, or 0 if it is not. */ +utf8_constexpr14 int utf8islower(utf8_int32_t chr); + +/* Returns 1 if the given character is uppercase, or 0 if it is not. */ +utf8_constexpr14 int utf8isupper(utf8_int32_t chr); + +/* Transform the given string into all lowercase codepoints. */ +utf8_nonnull utf8_weak void utf8lwr(utf8_int8_t *utf8_restrict str); + +/* Transform the given string into all uppercase codepoints. */ +utf8_nonnull utf8_weak void utf8upr(utf8_int8_t *utf8_restrict str); + +/* Make a codepoint lower case if possible. */ +utf8_constexpr14 utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp); + +/* Make a codepoint upper case if possible. */ +utf8_constexpr14 utf8_int32_t utf8uprcodepoint(utf8_int32_t cp); + +/* Sets out_codepoint to the current utf8 codepoint in str, and returns the + * address of the previous utf8 codepoint before the current one in str. */ +utf8_constexpr14 utf8_nonnull utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint); + +/* Duplicate the utf8 string src by getting its size, calling alloc_func_ptr to + * copy over data to a new buffer, and returning that. Or 0 if alloc_func_ptr + * returned null. */ +utf8_weak utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +/* Similar to utf8dup, except that at most n bytes of src are copied. If src is + * longer than n, only n bytes are copied and a null byte is added. + * + * Returns a new string if successful, 0 otherwise. */ +utf8_weak utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, + size_t), + utf8_int8_t *user_data); + +#undef utf8_weak +#undef utf8_pure +#undef utf8_nonnull + +utf8_constexpr14_impl int utf8casecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + for (;;) { + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + + /* lower the srcs if required */ + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + /* lower the srcs if required */ + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } +} + +utf8_int8_t *utf8cat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl utf8_int8_t *utf8chr(const utf8_int8_t *src, + utf8_int32_t chr) { + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've made c into a 2 utf8 codepoint string, one for the chr we are + * seeking, another for the null terminating byte. Now use utf8str to + * search */ + return utf8str(src, c); +} + +utf8_constexpr14_impl int utf8cmp(const utf8_int8_t *src1, + const utf8_int8_t *src2) { + while (('\0' != *src1) || ('\0' != *src2)) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_constexpr14_impl int utf8coll(const utf8_int8_t *src1, + const utf8_int8_t *src2); + +utf8_int8_t *utf8cpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src) { + utf8_int8_t *d = dst; + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + while ('\0' != *src) { + *d++ = *src++; + } + + /* append null terminating byte */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl size_t utf8cspn(const utf8_int8_t *src, + const utf8_int8_t *reject) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *r = reject; + size_t offset = 0; + + while ('\0' != *r) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *r)) && (0 < offset)) { + return chars; + } else { + if (*r == src[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + r++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + r++; + } while (0x80 == (0xc0 & *r)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *r, so didn't get a chance to test it */ + if (0 < offset) { + return chars; + } + + /* the current utf8 codepoint in src did not match reject, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + src++; + } while ((0x80 == (0xc0 & *src))); + chars++; + } + + return chars; +} + +utf8_int8_t *utf8dup(const utf8_int8_t *src) { + return utf8dup_ex(src, utf8_null, utf8_null); +} + +utf8_int8_t *utf8dup_ex(const utf8_int8_t *src, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *n = utf8_null; + + /* figure out how many bytes (including the terminator) we need to copy first + */ + size_t bytes = utf8size(src); + + if (alloc_func_ptr) { + n = alloc_func_ptr(user_data, bytes); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + n = (utf8_int8_t *)malloc(bytes); +#else + return utf8_null; +#endif + } + + if (utf8_null == n) { + /* out of memory so we bail */ + return utf8_null; + } else { + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes]) { + n[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + n[bytes] = '\0'; + return n; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8fry(const utf8_int8_t *str); + +utf8_constexpr14_impl size_t utf8len(const utf8_int8_t *str) { + return utf8nlen(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nlen(const utf8_int8_t *str, size_t n) { + const utf8_int8_t *t = str; + size_t length = 0; + + while ((size_t)(str - t) < n && '\0' != *str) { + if (0xf0 == (0xf8 & *str)) { + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else { /* if (0x00 == (0x80 & *s)) { */ + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } + + /* no matter the bytes we marched s forward by, it was + * only 1 utf8 codepoint */ + length++; + } + + if ((size_t)(str - t) > n) { + length--; + } + return length; +} + +utf8_constexpr14_impl int utf8ncasecmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + utf8_int32_t src1_lwr_cp = 0, src2_lwr_cp = 0, src1_upr_cp = 0, + src2_upr_cp = 0, src1_orig_cp = 0, src2_orig_cp = 0; + + do { + const utf8_int8_t *const s1 = src1; + const utf8_int8_t *const s2 = src2; + + /* first check that we have enough bytes left in n to contain an entire + * codepoint */ + if (0 == n) { + return 0; + } + + if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) { + const utf8_int32_t c1 = (0xe0 & *s1); + const utf8_int32_t c2 = (0xe0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) { + const utf8_int32_t c1 = (0xf0 & *s1); + const utf8_int32_t c2 = (0xf0 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) { + const utf8_int32_t c1 = (0xf8 & *s1); + const utf8_int32_t c2 = (0xf8 & *s2); + + if (c1 != c2) { + return c1 - c2; + } else { + return 0; + } + } + + src1 = utf8codepoint(src1, &src1_orig_cp); + src2 = utf8codepoint(src2, &src2_orig_cp); + n -= utf8codepointsize(src1_orig_cp); + + src1_lwr_cp = utf8lwrcodepoint(src1_orig_cp); + src2_lwr_cp = utf8lwrcodepoint(src2_orig_cp); + + src1_upr_cp = utf8uprcodepoint(src1_orig_cp); + src2_upr_cp = utf8uprcodepoint(src2_orig_cp); + + /* check if the lowered codepoints match */ + if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) { + return 0; + } else if ((src1_lwr_cp == src2_lwr_cp) || (src1_upr_cp == src2_upr_cp)) { + continue; + } + + /* if they don't match, then we return the difference between the characters + */ + return src1_lwr_cp - src2_lwr_cp; + } while (0 < n); + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncat(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + + /* find the null terminating byte in dst */ + while ('\0' != *d) { + d++; + } + + /* overwriting the null terminating byte in dst, append src byte-by-byte + * stopping if we run out of space */ + while (('\0' != *src) && (0 != n--)) { + *d++ = *src++; + } + + /* write out a new null terminating byte into dst */ + *d = '\0'; + + return dst; +} + +utf8_constexpr14_impl int utf8ncmp(const utf8_int8_t *src1, + const utf8_int8_t *src2, size_t n) { + while ((0 != n--) && (('\0' != *src1) || ('\0' != *src2))) { + if (*src1 < *src2) { + return -1; + } else if (*src1 > *src2) { + return 1; + } + + src1++; + src2++; + } + + /* both utf8 strings matched */ + return 0; +} + +utf8_int8_t *utf8ncpy(utf8_int8_t *utf8_restrict dst, + const utf8_int8_t *utf8_restrict src, size_t n) { + utf8_int8_t *d = dst; + size_t index = 0, check_index = 0; + + if (n == 0) { + return dst; + } + + /* overwriting anything previously in dst, write byte-by-byte + * from src */ + for (index = 0; index < n; index++) { + d[index] = src[index]; + if ('\0' == src[index]) { + break; + } + } + + for (check_index = index - 1; + check_index > 0 && 0x80 == (0xc0 & d[check_index]); check_index--) { + /* just moving the index */ + } + + if (check_index < index && + ((index - check_index) < utf8codepointcalcsize(&d[check_index]) || + (index - check_index) == n)) { + index = check_index; + } + + /* append null terminating byte */ + for (; index < n; index++) { + d[index] = 0; + } + + return dst; +} + +utf8_int8_t *utf8ndup(const utf8_int8_t *src, size_t n) { + return utf8ndup_ex(src, n, utf8_null, utf8_null); +} + +utf8_int8_t *utf8ndup_ex(const utf8_int8_t *src, size_t n, + utf8_int8_t *(*alloc_func_ptr)(utf8_int8_t *, size_t), + utf8_int8_t *user_data) { + utf8_int8_t *c = utf8_null; + size_t bytes = 0; + + /* Find the end of the string or stop when n is reached */ + while ('\0' != src[bytes] && bytes < n) { + bytes++; + } + + /* In case bytes is actually less than n, we need to set it + * to be used later in the copy byte by byte. */ + n = bytes; + + if (alloc_func_ptr) { + c = alloc_func_ptr(user_data, bytes + 1); + } else { +#if !defined(UTF8_NO_STD_MALLOC) + c = (utf8_int8_t *)malloc(bytes + 1); +#else + c = utf8_null; +#endif + } + + if (utf8_null == c) { + /* out of memory so we bail */ + return utf8_null; + } + + bytes = 0; + + /* copy src byte-by-byte into our new utf8 string */ + while ('\0' != src[bytes] && bytes < n) { + c[bytes] = src[bytes]; + bytes++; + } + + /* append null terminating byte */ + c[bytes] = '\0'; + return c; +} + +utf8_constexpr14_impl utf8_int8_t *utf8rchr(const utf8_int8_t *src, int chr) { + + utf8_int8_t *match = utf8_null; + utf8_int8_t c[5] = {'\0', '\0', '\0', '\0', '\0'}; + + if (0 == chr) { + /* being asked to return position of null terminating byte, so + * just run s to the end, and return! */ + while ('\0' != *src) { + src++; + } + return (utf8_int8_t *)src; + } else if (0 == ((int)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + c[0] = (utf8_int8_t)chr; + } else if (0 == ((int)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)(chr >> 6)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else if (0 == ((int)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)(chr >> 12)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + c[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)(chr >> 18)); + c[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + c[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + c[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + } + + /* we've created a 2 utf8 codepoint string in c that is + * the utf8 character asked for by chr, and a null + * terminating byte */ + + while ('\0' != *src) { + size_t offset = 0; + + while ((src[offset] == c[offset]) && ('\0' != src[offset])) { + offset++; + } + + if ('\0' == c[offset]) { + /* we found a matching utf8 code point */ + match = (utf8_int8_t *)src; + src += offset; + + if ('\0' == *src) { + break; + } + } else { + src += offset; + + /* need to march s along to next utf8 codepoint start + * (the next byte that doesn't match 0b10xxxxxx) */ + if ('\0' != *src) { + do { + src++; + } while (0x80 == (0xc0 & *src)); + } + } + } + + /* return the last match we found (or 0 if no match was found) */ + return match; +} + +utf8_constexpr14_impl utf8_int8_t *utf8pbrk(const utf8_int8_t *str, + const utf8_int8_t *accept) { + while ('\0' != *str) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *a is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + return (utf8_int8_t *)str; + } else { + if (*a == str[offset]) { + /* part of a utf8 codepoint matched, so move our checking + * onwards to the next byte */ + offset++; + a++; + } else { + /* r could be in the middle of an unmatching utf8 code point, + * so we need to march it on to the next character beginning, */ + + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* we found a match on the last utf8 codepoint */ + if (0 < offset) { + return (utf8_int8_t *)str; + } + + /* the current utf8 codepoint in src did not match accept, but src + * could have been partway through a utf8 codepoint, so we need to + * march it onto the next utf8 codepoint starting byte */ + do { + str++; + } while ((0x80 == (0xc0 & *str))); + } + + return utf8_null; +} + +utf8_constexpr14_impl size_t utf8size(const utf8_int8_t *str) { + return utf8size_lazy(str) + 1; +} + +utf8_constexpr14_impl size_t utf8size_lazy(const utf8_int8_t *str) { + return utf8nsize_lazy(str, SIZE_MAX); +} + +utf8_constexpr14_impl size_t utf8nsize_lazy(const utf8_int8_t *str, size_t n) { + size_t size = 0; + while (size < n && '\0' != str[size]) { + size++; + } + return size; +} + +utf8_constexpr14_impl size_t utf8spn(const utf8_int8_t *src, + const utf8_int8_t *accept) { + size_t chars = 0; + + while ('\0' != *src) { + const utf8_int8_t *a = accept; + size_t offset = 0; + + while ('\0' != *a) { + /* checking that if *r is the start of a utf8 codepoint + * (it is not 0b10xxxxxx) and we have successfully matched + * a previous character (0 < offset) - we found a match */ + if ((0x80 != (0xc0 & *a)) && (0 < offset)) { + /* found a match, so increment the number of utf8 codepoints + * that have matched and stop checking whether any other utf8 + * codepoints in a match */ + chars++; + src += offset; + offset = 0; + break; + } else { + if (*a == src[offset]) { + offset++; + a++; + } else { + /* a could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning, */ + do { + a++; + } while (0x80 == (0xc0 & *a)); + + /* reset offset too as we found a mismatch */ + offset = 0; + } + } + } + + /* found a match at the end of *a, so didn't get a chance to test it */ + if (0 < offset) { + chars++; + src += offset; + continue; + } + + /* if a got to its terminating null byte, then we didn't find a match. + * Return the current number of matched utf8 codepoints */ + if ('\0' == *a) { + return chars; + } + } + + return chars; +} + +utf8_constexpr14_impl utf8_int8_t *utf8str(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + utf8_int32_t throwaway_codepoint = 0; + + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + while ('\0' != *haystack) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + + while (*haystack == *n && (*haystack != '\0' && *n != '\0')) { + n++; + haystack++; + } + + if ('\0' == *n) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } else { + /* h could be in the middle of an unmatching utf8 codepoint, + * so we need to march it on to the next character beginning + * starting from the current character */ + haystack = utf8codepoint(maybeMatch, &throwaway_codepoint); + } + } + + /* no match */ + return utf8_null; +} + +utf8_constexpr14_impl utf8_int8_t *utf8casestr(const utf8_int8_t *haystack, + const utf8_int8_t *needle) { + /* if needle has no utf8 codepoints before the null terminating + * byte then return haystack */ + if ('\0' == *needle) { + return (utf8_int8_t *)haystack; + } + + for (;;) { + const utf8_int8_t *maybeMatch = haystack; + const utf8_int8_t *n = needle; + utf8_int32_t h_cp = 0, n_cp = 0; + + /* Get the next code point and track it */ + const utf8_int8_t *nextH = haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + + while ((0 != h_cp) && (0 != n_cp)) { + h_cp = utf8lwrcodepoint(h_cp); + n_cp = utf8lwrcodepoint(n_cp); + + /* if we find a mismatch, bail out! */ + if (h_cp != n_cp) { + break; + } + + haystack = utf8codepoint(haystack, &h_cp); + n = utf8codepoint(n, &n_cp); + } + + if (0 == n_cp) { + /* we found the whole utf8 string for needle in haystack at + * maybeMatch, so return it */ + return (utf8_int8_t *)maybeMatch; + } + + if (0 == h_cp) { + /* no match */ + return utf8_null; + } + + /* Roll back to the next code point in the haystack to test */ + haystack = nextH; + } +} + +utf8_constexpr14_impl utf8_int8_t *utf8valid(const utf8_int8_t *str) { + return utf8nvalid(str, SIZE_MAX); +} + +utf8_constexpr14_impl utf8_int8_t *utf8nvalid(const utf8_int8_t *str, + size_t n) { + const utf8_int8_t *t = str; + size_t consumed = 0; + + while ((void)(consumed = (size_t)(str - t)), consumed < n && '\0' != *str) { + const size_t remaining = n - consumed; + + if (0xf0 == (0xf8 & *str)) { + /* ensure that there's 4 bytes or more remaining */ + if (remaining < 4) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2])) || + (0x80 != (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 4 bytes */ + if ((remaining != 4) && (0x80 == (0xc0 & str[4]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 4-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x07 & str[0])) && (0 == (0x30 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + str += 4; + } else if (0xe0 == (0xf0 & *str)) { + /* ensure that there's 3 bytes or more remaining */ + if (remaining < 3) { + return (utf8_int8_t *)str; + } + + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & str[1])) || (0x80 != (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 3 bytes */ + if ((remaining != 3) && (0x80 == (0xc0 & str[3]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 5 bits of this 3-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if ((0 == (0x0f & str[0])) && (0 == (0x20 & str[1]))) { + return (utf8_int8_t *)str; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + str += 3; + } else if (0xc0 == (0xe0 & *str)) { + /* ensure that there's 2 bytes or more remaining */ + if (remaining < 2) { + return (utf8_int8_t *)str; + } + + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & str[1])) { + return (utf8_int8_t *)str; + } + + /* ensure that our utf8 codepoint ended after 2 bytes */ + if ((remaining != 2) && (0x80 == (0xc0 & str[2]))) { + return (utf8_int8_t *)str; + } + + /* ensure that the top 4 bits of this 2-byte utf8 + * codepoint were not 0, as then we could have used + * one of the smaller encodings */ + if (0 == (0x1e & str[0])) { + return (utf8_int8_t *)str; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + str += 2; + } else if (0x00 == (0x80 & *str)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + str += 1; + } else { + /* we have an invalid 0b1xxxxxxx utf8 code point entry */ + return (utf8_int8_t *)str; + } + } + + return utf8_null; +} + +int utf8makevalid(utf8_int8_t *str, const utf8_int32_t replacement) { + utf8_int8_t *read = str; + utf8_int8_t *write = read; + const utf8_int8_t r = (utf8_int8_t)replacement; + utf8_int32_t codepoint = 0; + + if (replacement > 0x7f) { + return -1; + } + + while ('\0' != *read) { + if (0xf0 == (0xf8 & *read)) { + /* ensure each of the 3 following bytes in this 4-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2])) || + (0x80 != (0xc0 & read[3]))) { + *write++ = r; + read++; + continue; + } + + /* 4-byte utf8 code point (began with 0b11110xxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 4); + } else if (0xe0 == (0xf0 & *read)) { + /* ensure each of the 2 following bytes in this 3-byte + * utf8 codepoint began with 0b10xxxxxx */ + if ((0x80 != (0xc0 & read[1])) || (0x80 != (0xc0 & read[2]))) { + *write++ = r; + read++; + continue; + } + + /* 3-byte utf8 code point (began with 0b1110xxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 3); + } else if (0xc0 == (0xe0 & *read)) { + /* ensure the 1 following byte in this 2-byte + * utf8 codepoint began with 0b10xxxxxx */ + if (0x80 != (0xc0 & read[1])) { + *write++ = r; + read++; + continue; + } + + /* 2-byte utf8 code point (began with 0b110xxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 2); + } else if (0x00 == (0x80 & *read)) { + /* 1-byte ascii (began with 0b0xxxxxxx) */ + read = utf8codepoint(read, &codepoint); + write = utf8catcodepoint(write, codepoint, 1); + } else { + /* if we got here then we've got a dangling continuation (0b10xxxxxx) */ + *write++ = r; + read++; + continue; + } + } + + *write = '\0'; + + return 0; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8codepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | + ((0x3f & str[2]) << 6) | (0x3f & str[3]); + str += 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]); + str += 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]); + str += 2; + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = str[0]; + str += 1; + } + + return (utf8_int8_t *)str; +} + +utf8_constexpr14_impl size_t utf8codepointcalcsize(const utf8_int8_t *str) { + if (0xf0 == (0xf8 & str[0])) { + /* 4 byte utf8 codepoint */ + return 4; + } else if (0xe0 == (0xf0 & str[0])) { + /* 3 byte utf8 codepoint */ + return 3; + } else if (0xc0 == (0xe0 & str[0])) { + /* 2 byte utf8 codepoint */ + return 2; + } + + /* 1 byte utf8 codepoint otherwise */ + return 1; +} + +utf8_constexpr14_impl size_t utf8codepointsize(utf8_int32_t chr) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + return 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + return 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + return 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + return 4; + } +} + +utf8_int8_t *utf8catcodepoint(utf8_int8_t *str, utf8_int32_t chr, size_t n) { + if (0 == ((utf8_int32_t)0xffffff80 & chr)) { + /* 1-byte/7-bit ascii + * (0b0xxxxxxx) */ + if (n < 1) { + return utf8_null; + } + str[0] = (utf8_int8_t)chr; + str += 1; + } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) { + /* 2-byte/11-bit utf8 code point + * (0b110xxxxx 0b10xxxxxx) */ + if (n < 2) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xc0 | (utf8_int8_t)((chr >> 6) & 0x1f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 2; + } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) { + /* 3-byte/16-bit utf8 code point + * (0b1110xxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 3) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xe0 | (utf8_int8_t)((chr >> 12) & 0x0f)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 3; + } else { /* if (0 == ((int)0xffe00000 & chr)) { */ + /* 4-byte/21-bit utf8 code point + * (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx) */ + if (n < 4) { + return utf8_null; + } + str[0] = (utf8_int8_t)(0xf0 | (utf8_int8_t)((chr >> 18) & 0x07)); + str[1] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 12) & 0x3f)); + str[2] = (utf8_int8_t)(0x80 | (utf8_int8_t)((chr >> 6) & 0x3f)); + str[3] = (utf8_int8_t)(0x80 | (utf8_int8_t)(chr & 0x3f)); + str += 4; + } + + return str; +} + +utf8_constexpr14_impl int utf8islower(utf8_int32_t chr) { + return chr != utf8uprcodepoint(chr); +} + +utf8_constexpr14_impl int utf8isupper(utf8_int32_t chr) { + return chr != utf8lwrcodepoint(chr); +} + +void utf8lwr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +void utf8upr(utf8_int8_t *utf8_restrict str) { + utf8_int32_t cp = 0; + utf8_int8_t *pn = utf8codepoint(str, &cp); + + while (cp != 0) { + const utf8_int32_t lwr_cp = utf8uprcodepoint(cp); + const size_t size = utf8codepointsize(lwr_cp); + + if (lwr_cp != cp) { + utf8catcodepoint(str, lwr_cp, size); + } + + str = pn; + pn = utf8codepoint(str, &cp); + } +} + +utf8_constexpr14_impl utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) { + if (((0x0041 <= cp) && (0x005a >= cp)) || + ((0x00c0 <= cp) && (0x00d6 >= cp)) || + ((0x00d8 <= cp) && (0x00de >= cp)) || + ((0x0391 <= cp) && (0x03a1 >= cp)) || + ((0x03a3 <= cp) && (0x03ab >= cp)) || + ((0x0410 <= cp) && (0x042f >= cp))) { + cp += 32; + } else if ((0x0400 <= cp) && (0x040f >= cp)) { + cp += 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp |= 0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp += 1; + cp &= ~0x1; + } else { + switch (cp) { + default: + break; + case 0x0178: + cp = 0x00ff; + break; + case 0x0243: + cp = 0x0180; + break; + case 0x018e: + cp = 0x01dd; + break; + case 0x023d: + cp = 0x019a; + break; + case 0x0220: + cp = 0x019e; + break; + case 0x01b7: + cp = 0x0292; + break; + case 0x01c4: + cp = 0x01c6; + break; + case 0x01c7: + cp = 0x01c9; + break; + case 0x01ca: + cp = 0x01cc; + break; + case 0x01f1: + cp = 0x01f3; + break; + case 0x01f7: + cp = 0x01bf; + break; + case 0x0187: + cp = 0x0188; + break; + case 0x018b: + cp = 0x018c; + break; + case 0x0191: + cp = 0x0192; + break; + case 0x0198: + cp = 0x0199; + break; + case 0x01a7: + cp = 0x01a8; + break; + case 0x01ac: + cp = 0x01ad; + break; + case 0x01b8: + cp = 0x01b9; + break; + case 0x01bc: + cp = 0x01bd; + break; + case 0x01f4: + cp = 0x01f5; + break; + case 0x023b: + cp = 0x023c; + break; + case 0x0241: + cp = 0x0242; + break; + case 0x03fd: + cp = 0x037b; + break; + case 0x03fe: + cp = 0x037c; + break; + case 0x03ff: + cp = 0x037d; + break; + case 0x037f: + cp = 0x03f3; + break; + case 0x0386: + cp = 0x03ac; + break; + case 0x0388: + cp = 0x03ad; + break; + case 0x0389: + cp = 0x03ae; + break; + case 0x038a: + cp = 0x03af; + break; + case 0x038c: + cp = 0x03cc; + break; + case 0x038e: + cp = 0x03cd; + break; + case 0x038f: + cp = 0x03ce; + break; + case 0x0370: + cp = 0x0371; + break; + case 0x0372: + cp = 0x0373; + break; + case 0x0376: + cp = 0x0377; + break; + case 0x03f4: + cp = 0x03b8; + break; + case 0x03cf: + cp = 0x03d7; + break; + case 0x03f9: + cp = 0x03f2; + break; + case 0x03f7: + cp = 0x03f8; + break; + case 0x03fa: + cp = 0x03fb; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) { + if (((0x0061 <= cp) && (0x007a >= cp)) || + ((0x00e0 <= cp) && (0x00f6 >= cp)) || + ((0x00f8 <= cp) && (0x00fe >= cp)) || + ((0x03b1 <= cp) && (0x03c1 >= cp)) || + ((0x03c3 <= cp) && (0x03cb >= cp)) || + ((0x0430 <= cp) && (0x044f >= cp))) { + cp -= 32; + } else if ((0x0450 <= cp) && (0x045f >= cp)) { + cp -= 80; + } else if (((0x0100 <= cp) && (0x012f >= cp)) || + ((0x0132 <= cp) && (0x0137 >= cp)) || + ((0x014a <= cp) && (0x0177 >= cp)) || + ((0x0182 <= cp) && (0x0185 >= cp)) || + ((0x01a0 <= cp) && (0x01a5 >= cp)) || + ((0x01de <= cp) && (0x01ef >= cp)) || + ((0x01f8 <= cp) && (0x021f >= cp)) || + ((0x0222 <= cp) && (0x0233 >= cp)) || + ((0x0246 <= cp) && (0x024f >= cp)) || + ((0x03d8 <= cp) && (0x03ef >= cp)) || + ((0x0460 <= cp) && (0x0481 >= cp)) || + ((0x048a <= cp) && (0x04ff >= cp))) { + cp &= ~0x1; + } else if (((0x0139 <= cp) && (0x0148 >= cp)) || + ((0x0179 <= cp) && (0x017e >= cp)) || + ((0x01af <= cp) && (0x01b0 >= cp)) || + ((0x01b3 <= cp) && (0x01b6 >= cp)) || + ((0x01cd <= cp) && (0x01dc >= cp))) { + cp -= 1; + cp |= 0x1; + } else { + switch (cp) { + default: + break; + case 0x00ff: + cp = 0x0178; + break; + case 0x0180: + cp = 0x0243; + break; + case 0x01dd: + cp = 0x018e; + break; + case 0x019a: + cp = 0x023d; + break; + case 0x019e: + cp = 0x0220; + break; + case 0x0292: + cp = 0x01b7; + break; + case 0x01c6: + cp = 0x01c4; + break; + case 0x01c9: + cp = 0x01c7; + break; + case 0x01cc: + cp = 0x01ca; + break; + case 0x01f3: + cp = 0x01f1; + break; + case 0x01bf: + cp = 0x01f7; + break; + case 0x0188: + cp = 0x0187; + break; + case 0x018c: + cp = 0x018b; + break; + case 0x0192: + cp = 0x0191; + break; + case 0x0199: + cp = 0x0198; + break; + case 0x01a8: + cp = 0x01a7; + break; + case 0x01ad: + cp = 0x01ac; + break; + case 0x01b9: + cp = 0x01b8; + break; + case 0x01bd: + cp = 0x01bc; + break; + case 0x01f5: + cp = 0x01f4; + break; + case 0x023c: + cp = 0x023b; + break; + case 0x0242: + cp = 0x0241; + break; + case 0x037b: + cp = 0x03fd; + break; + case 0x037c: + cp = 0x03fe; + break; + case 0x037d: + cp = 0x03ff; + break; + case 0x03f3: + cp = 0x037f; + break; + case 0x03ac: + cp = 0x0386; + break; + case 0x03ad: + cp = 0x0388; + break; + case 0x03ae: + cp = 0x0389; + break; + case 0x03af: + cp = 0x038a; + break; + case 0x03cc: + cp = 0x038c; + break; + case 0x03cd: + cp = 0x038e; + break; + case 0x03ce: + cp = 0x038f; + break; + case 0x0371: + cp = 0x0370; + break; + case 0x0373: + cp = 0x0372; + break; + case 0x0377: + cp = 0x0376; + break; + case 0x03d1: + cp = 0x0398; + break; + case 0x03d7: + cp = 0x03cf; + break; + case 0x03f2: + cp = 0x03f9; + break; + case 0x03f8: + cp = 0x03f7; + break; + case 0x03fb: + cp = 0x03fa; + break; + } + } + + return cp; +} + +utf8_constexpr14_impl utf8_int8_t * +utf8rcodepoint(const utf8_int8_t *utf8_restrict str, + utf8_int32_t *utf8_restrict out_codepoint) { + const utf8_int8_t *s = (const utf8_int8_t *)str; + + if (0xf0 == (0xf8 & s[0])) { + /* 4 byte utf8 codepoint */ + *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | + ((0x3f & s[2]) << 6) | (0x3f & s[3]); + } else if (0xe0 == (0xf0 & s[0])) { + /* 3 byte utf8 codepoint */ + *out_codepoint = + ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); + } else if (0xc0 == (0xe0 & s[0])) { + /* 2 byte utf8 codepoint */ + *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); + } else { + /* 1 byte utf8 codepoint otherwise */ + *out_codepoint = s[0]; + } + + do { + s--; + } while ((0 != (0x80 & s[0])) && (0x80 == (0xc0 & s[0]))); + + return (utf8_int8_t *)s; +} + +#undef utf8_restrict +#undef utf8_constexpr14 +#undef utf8_null + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif /* SHEREDOM_UTF8_H_INCLUDED */ diff --git a/src/win32/graphics_win32.c b/src/win32/graphics_win32.c new file mode 100644 index 0000000..3dbd913 --- /dev/null +++ b/src/win32/graphics_win32.c @@ -0,0 +1,139 @@ +#include + +HANDLE hStdout; + +#define TC_NRM "\x1B[0m" /* Normalize color */ + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + +#define clear_entire_line() fputs("\x1B[2K") +#define clear_line_till_cursor() fputs("\x1B[1K") +#define clear_line_from_cursor() fputs("\x1B[0K") + +void color_id(uint8_t cid, int l) +{ + printf((l) ? "\x1B[38;5;%dm" : "\x1B[48;5;%dm", cid); +} + +void set_color(Color color) +{ + if (color.background) + { + printf("\x1B[48;2;%d;%d;%dm", color.r, color.g, color.b); + } + else + { + printf("\x1B[38;2;%d;%d;%dm", color.r, color.g, color.b); + } +} + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +#define echo_off() +#define echo_on() +#define canon_off() +#define canon_on() + +void get_cursor(int *X, int *Y) +{ + echo_off(); + canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +void gotoxy(int x, int y) +{ + printf("\033[%d;%df", y+1, x+1); +} +void move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define clear_screen() fputs("\x1B[2J", stdout) +#define clear_from_top_to_cursor() fputs("\x1B[1J", stdout) +#define clear_from_cursor_to_bottom() fputs("\x1B[0J", stdout) + +void clear_partial(int x, int y, int width, int height) +{ + char *buf = calloc(width + 1, sizeof(char)); + memset(buf, (char) ' ', width); + for (int i = 0; i < height; i++) + { + gotoxy(x, y + i); + fwrite(buf, width, sizeof(char), stdout); + } + gotoxy(x, y); + free(buf); +} + +void get_cols_rows(size_t *cols, size_t *rows) +{ + if (hStdout == INVALID_HANDLE_VALUE) + { + return; + } + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) + { + return; + } + *cols = (size_t) csbi.srWindow.Right - csbi.srWindow.Left + 1; + *rows = (size_t) csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +} + +int alternate_buffer(bool enabled) +{ + fputs(enabled ? "\033[?1049h" : "\033[?1049l", stdout); + return enabled; +} diff --git a/src/win32/graphics_win32.h b/src/win32/graphics_win32.h new file mode 100644 index 0000000..2f07f20 --- /dev/null +++ b/src/win32/graphics_win32.h @@ -0,0 +1,140 @@ +#include +#include + +HANDLE hStdout; + +#define TC_NRM "\x1B[0m" /* Normalize color */ + +#define TC_RED "\x1B[1;31m" /* Red */ +#define TC_GRN "\x1B[1;32m" /* Green */ +#define TC_YEL "\x1B[1;33m" /* Yellow */ +#define TC_BLU "\x1B[1;34m" /* Blue */ +#define TC_MAG "\x1B[1;35m" /* Magenta */ +#define TC_CYN "\x1B[1;36m" /* Cyan */ +#define TC_WHT "\x1B[1;37m" /* White */ + +#define TC_B_NRM "\x1B[0m" /* Normalize Bright Color */ +#define TC_B_RED "\x1B[0;31m" /* Bright Red */ +#define TC_B_GRN "\x1B[0;32m" /* Bright Green */ +#define TC_B_YEL "\x1B[0;33m" /* Bright Yellow */ +#define TC_B_BLU "\x1B[0;34m" /* Bright Blue */ +#define TC_B_MAG "\x1B[0;35m" /* Bright Magenta */ +#define TC_B_CYN "\x1B[0;36m" /* Bright Cyan */ +#define TC_B_WHT "\x1B[0;37m" /* Bright White */ + +#define TC_BG_NRM "\x1B[40m" /* Normalize Background Color */ +#define TC_BG_RED "\x1B[41m" /* Background Red */ +#define TC_BG_GRN "\x1B[42m" /* Background Green */ +#define TC_BG_YEL "\x1B[43m" /* Background Yellow */ +#define TC_BG_BLU "\x1B[44m" /* Background Blue */ +#define TC_BG_MAG "\x1B[45m" /* Background Magenta*/ +#define TC_BG_CYN "\x1B[46m" /* Background Cyan */ +#define TC_BG_WHT "\x1B[47m" /* Background White */ + +#define clear_entire_line() fputs("\x1B[2K") +#define clear_line_till_cursor() fputs("\x1B[1K") +#define clear_line_from_cursor() fputs("\x1B[0K") + +void color_id(uint8_t cid, int l) +{ + printf((l) ? "\x1B[38;5;%dm" : "\x1B[48;5;%dm", cid); +} + +void set_color(Color color) +{ + if (color.background) + { + printf("\x1B[48;2;%d;%d;%dm", color.r, color.g, color.b); + } + else + { + printf("\x1B[38;2;%d;%d;%dm", color.r, color.g, color.b); + } +} + +////////////////////////////////////// +// Additional formatting (ANSI) // +////////////////////////////////////// + +#define TC_BLD "\x1B[1m" /* Bold */ +#define TC_DIM "\x1B[2m" /* Dim */ +#define TC_ITAL "\x1B[3m" /* Standout (italics) */ +#define TC_UNDR "\x1B[4m" /* Underline */ +#define TC_BLNK "\x1B[5m" /* Blink */ +#define TC_REV "\x1B[7m" /* Reverse */ +#define TC_INV "\x1B[8m" /* Invisible */ + +#define echo_off() +#define echo_on() +#define canon_off() +#define canon_on() + +void get_cursor(int *X, int *Y) +{ + echo_off(); + canon_off(); + printf("\033[6n"); + scanf("\033[%d;%dR", X, Y); +} +void gotoxy(int x, int y) +{ + printf("\033[%d;%df", y+1, x+1); +} +void move_cursor(int X, int Y) +{ + if (X > 0) + { + printf("\033[%dC", X); + } + else if (X < 0) + { + printf("\033[%dD", (X * -1)); + } + + if (Y > 0) + { + printf("\033[%dB", Y); + } + else if (Y < 0) + { + printf("\033[%dA", (Y * -1)); + } +} + +#define clear_screen() fputs("\x1B[2J", stdout) +#define clear_from_top_to_cursor() fputs("\x1B[1J", stdout) +#define clear_from_cursor_to_bottom() fputs("\x1B[0J", stdout) + +void clear_partial(int x, int y, int width, int height) +{ + char *buf = calloc(width + 1, sizeof(char)); + memset(buf, (char) ' ', width); + for (int i = 0; i < height; i++) + { + gotoxy(x, y + i); + fwrite(buf, width, sizeof(char), stdout); + } + gotoxy(x, y); + free(buf); +} + +void get_cols_rows(size_t *cols, size_t *rows) +{ + if (hStdout == INVALID_HANDLE_VALUE) + { + return; + } + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) + { + return; + } + *cols = (size_t) csbi.srWindow.Right - csbi.srWindow.Left + 1; + *rows = (size_t) csbi.srWindow.Bottom - csbi.srWindow.Top + 1; +} + +int alternate_buffer(bool enabled) +{ + fputs(enabled ? "\033[?1049h" : "\033[?1049l", stdout); + return enabled; +} diff --git a/src/win32/input_win32.c b/src/win32/input_win32.c new file mode 100644 index 0000000..32c5fd2 --- /dev/null +++ b/src/win32/input_win32.c @@ -0,0 +1,171 @@ +HANDLE hStdin; + +int getch_n() +{ + int gc_n = 0; + gc_n = getch(); + if (gc_n == 0) + { + gc_n = getch(); + gc_n |= BIT_ESC0; + } + else if (gc_n == 0xE0) + { + gc_n = getch(); + gc_n |= BIT_ESC224; + } + + return gc_n; +} + +InputUTF8 get_newtrodit_input() +{ + DWORD fdwSaveOldMode, cNumRead; + INPUT_RECORD irInBuf[128]; + InputUTF8 inputchar = {0}; + + if (!GetConsoleMode(hStdin, &fdwSaveOldMode)) + return inputchar; + + SetConsoleMode(hStdin, 0); + long other_flags = 0; + + int ukey_counter = 0, waiting_combination = FALSE; + const int replacement_char = 0xefbfbd; // Replacement char is in hexadecimal EF BF BD + static size_t pushback_char_count = 0; + static INPUT_RECORD pushback_inputchar[128] = {0}; + INPUT_RECORD *ptr_irInBuf = NULL; + char unicodestr[5] = {0}; + while (1) + { + // Wait for the input events + + WaitForSingleObject(hStdin, INFINITE); + + if (pushback_char_count > 0) + { + cNumRead = pushback_char_count; + } + else + { + if (!ReadConsoleInput(hStdin, irInBuf, 128, &cNumRead)) + return inputchar; + } + + for (DWORD i = 0; i < cNumRead; i++) + { + if (pushback_char_count > 0) + { + ptr_irInBuf = &pushback_inputchar[0]; + + for (size_t j = 0; j < pushback_char_count - 1; j++) + pushback_inputchar[j] = pushback_inputchar[j + 1]; + + pushback_char_count--; + } + else + { + ptr_irInBuf = &irInBuf[i]; + } + + switch (ptr_irInBuf->EventType) + { + case KEY_EVENT: // We are only interested in keyboard input + if (ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar) + { + if (inputchar.utf8char == replacement_char) // If we receive the UTF-8 replacement character, stop the input + break; + + ukey_counter++; + if (ptr_irInBuf->Event.KeyEvent.bKeyDown && ukey_counter <= 4 && (((ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff) < 0x80 && cNumRead <= 1) || ((ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff) > 0x80 && cNumRead > 1))) + { + inputchar.utf8char <<= 8; + inputchar.utf8char |= (ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff); + } + else + { + if (ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + if (pushback_char_count < 127 && pushback_char_count < cNumRead) + pushback_inputchar[pushback_char_count++] = *ptr_irInBuf; + } + } + waiting_combination = FALSE; + } + else + { + if (ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + switch (ptr_irInBuf->Event.KeyEvent.wVirtualKeyCode) + { + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + other_flags |= CTRL_BITMASK; + waiting_combination = TRUE; + break; + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + other_flags |= SHIFT_BITMASK; + waiting_combination = TRUE; + break; + case VK_LWIN: // Ignore Windows keys + case VK_RWIN: + break; + case VK_MENU: // Windows calls ALT keys 'menu keys' for some reason + case VK_LMENU: + case VK_RMENU: + other_flags |= ALT_BITMASK; + waiting_combination = TRUE; + break; + default: + inputchar.flags <<= 8; + inputchar.flags |= ptr_irInBuf->Event.KeyEvent.wVirtualKeyCode; // If not one of the above cases, save the virtual key code + break; + } + } + else + { + other_flags = 0; + } + } + break; + } + } + + if (!(other_flags && inputchar.flags == 0)) + waiting_combination = FALSE; + + if (!waiting_combination) + { + if (GetKeyState(VK_CONTROL) & 0x8000) // Make sure the key is still pressed, for example, making another combination without releasing the control key + other_flags |= CTRL_BITMASK; + if (GetKeyState(VK_SHIFT) & 0x8000) // Same thing as above, now for shift + other_flags |= SHIFT_BITMASK; + if (GetKeyState(VK_MENU) & 0x8000) // ALT bitmask + other_flags |= ALT_BITMASK; + + if (ptr_irInBuf->EventType == KEY_EVENT && ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + inputchar.flags |= other_flags; // Add the other flags in case they are defined + + int32_to_char_array(inputchar.utf8char, unicodestr); + if (utf8valid(unicodestr) != 0) + { + inputchar.utf8char = 0; + inputchar.flags = -EILSEQ; // Invalid sequence + } + if (inputchar.utf8char != 0 && ukey_counter > 4) + { + inputchar.utf8char = 0; + inputchar.flags = replacement_char; // If a possibly invalid sequence is inputted, replace it with the replacement char + } + SetConsoleMode(hStdin, fdwSaveOldMode); + return inputchar; + } + } + + FlushConsoleInputBuffer(hStdin); // Flush the input buffer so previous keystrokes are ignored + } +} diff --git a/src/win32/input_win32.h b/src/win32/input_win32.h new file mode 100644 index 0000000..05533fc --- /dev/null +++ b/src/win32/input_win32.h @@ -0,0 +1,173 @@ +#include +#include "..\unicode.h" +HANDLE hStdin; + +int getch_n() +{ + int gc_n = 0; + gc_n = getch(); + if (gc_n == 0) + { + gc_n = getch(); + gc_n |= BIT_ESC0; + } + else if (gc_n == 0xE0) + { + gc_n = getch(); + gc_n |= BIT_ESC224; + } + + return gc_n; +} + +InputUTF8 get_newtrodit_input() +{ + DWORD fdwSaveOldMode, cNumRead; + INPUT_RECORD irInBuf[128]; + InputUTF8 inputchar = {0}; + + if (!GetConsoleMode(hStdin, &fdwSaveOldMode)) + return inputchar; + + SetConsoleMode(hStdin, 0); + long other_flags = 0; + + int ukey_counter = 0, waiting_combination = FALSE; + const int replacement_char = 0xefbfbd; // Replacement char is in hexadecimal EF BF BD + static size_t pushback_char_count = 0; + static INPUT_RECORD pushback_inputchar[128] = {0}; + INPUT_RECORD *ptr_irInBuf = NULL; + char unicodestr[5] = {0}; + while (1) + { + // Wait for the input events + + WaitForSingleObject(hStdin, INFINITE); + + if (pushback_char_count > 0) + { + cNumRead = pushback_char_count; + } + else + { + if (!ReadConsoleInput(hStdin, irInBuf, 128, &cNumRead)) + return inputchar; + } + + for (DWORD i = 0; i < cNumRead; i++) + { + if (pushback_char_count > 0) + { + ptr_irInBuf = &pushback_inputchar[0]; + + for (size_t j = 0; j < pushback_char_count - 1; j++) + pushback_inputchar[j] = pushback_inputchar[j + 1]; + + pushback_char_count--; + } + else + { + ptr_irInBuf = &irInBuf[i]; + } + + switch (ptr_irInBuf->EventType) + { + case KEY_EVENT: // We are only interested in keyboard input + if (ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar) + { + if (inputchar.utf8char == replacement_char) // If we receive the UTF-8 replacement character, stop the input + break; + + ukey_counter++; + if (ptr_irInBuf->Event.KeyEvent.bKeyDown && ukey_counter <= 4 && (((ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff) < 0x80 && cNumRead <= 1) || ((ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff) > 0x80 && cNumRead > 1))) + { + inputchar.utf8char <<= 8; + inputchar.utf8char |= (ptr_irInBuf->Event.KeyEvent.uChar.UnicodeChar & 0xff); + } + else + { + if (ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + if (pushback_char_count < 127 && pushback_char_count < cNumRead) + pushback_inputchar[pushback_char_count++] = *ptr_irInBuf; + } + } + waiting_combination = FALSE; + } + else + { + if (ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + switch (ptr_irInBuf->Event.KeyEvent.wVirtualKeyCode) + { + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + other_flags |= CTRL_BITMASK; + waiting_combination = TRUE; + break; + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + other_flags |= SHIFT_BITMASK; + waiting_combination = TRUE; + break; + case VK_LWIN: // Ignore Windows keys + case VK_RWIN: + break; + case VK_MENU: // Windows calls ALT keys 'menu keys' for some reason + case VK_LMENU: + case VK_RMENU: + other_flags |= ALT_BITMASK; + waiting_combination = TRUE; + break; + default: + inputchar.flags <<= 8; + inputchar.flags |= ptr_irInBuf->Event.KeyEvent.wVirtualKeyCode; // If not one of the above cases, save the virtual key code + break; + } + } + else + { + other_flags = 0; + } + } + break; + } + } + + if (!(other_flags && inputchar.flags == 0)) + waiting_combination = FALSE; + + if (!waiting_combination) + { + if (GetKeyState(VK_CONTROL) & 0x8000) // Make sure the key is still pressed, for example, making another combination without releasing the control key + other_flags |= CTRL_BITMASK; + if (GetKeyState(VK_SHIFT) & 0x8000) // Same thing as above, now for shift + other_flags |= SHIFT_BITMASK; + if (GetKeyState(VK_MENU) & 0x8000) // ALT bitmask + other_flags |= ALT_BITMASK; + + if (ptr_irInBuf->EventType == KEY_EVENT && ptr_irInBuf->Event.KeyEvent.bKeyDown) + { + inputchar.flags |= other_flags; // Add the other flags in case they are defined + + int32_to_char_array(inputchar.utf8char, unicodestr); + if (utf8valid(unicodestr) != 0) + { + inputchar.utf8char = 0; + inputchar.flags = -EILSEQ; // Invalid sequence + } + if (inputchar.utf8char != 0 && ukey_counter > 4) + { + inputchar.utf8char = 0; + inputchar.flags = replacement_char; // If a possibly invalid sequence is inputted, replace it with the replacement char + } + SetConsoleMode(hStdin, fdwSaveOldMode); + return inputchar; + } + } + + FlushConsoleInputBuffer(hStdin); // Flush the input buffer so previous keystrokes are ignored + } +}