aboutsummaryrefslogtreecommitdiffstats
path: root/common.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-01-24 14:11:01 +0100
committerMattias Andrée <m@maandree.se>2026-01-24 14:11:01 +0100
commitc8b7fdc7294329dc5eaf9f089f83184ece7d098c (patch)
tree1059fd3ab69fb9219ef5a6be3f53cd8208015c40 /common.c
downloadcharconv-c8b7fdc7294329dc5eaf9f089f83184ece7d098c.tar.gz
charconv-c8b7fdc7294329dc5eaf9f089f83184ece7d098c.tar.bz2
charconv-c8b7fdc7294329dc5eaf9f089f83184ece7d098c.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'common.c')
-rw-r--r--common.c169
1 files changed, 169 insertions, 0 deletions
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..11d72ac
--- /dev/null
+++ b/common.c
@@ -0,0 +1,169 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+void
+writeall(const char *s, size_t n)
+{
+ ssize_t r;
+ while (n) {
+ r = write(STDOUT_FILENO, s, n);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: write <stdout>: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+ s = &s[r];
+ n -= (size_t)r;
+ }
+}
+
+
+void
+writechar(uint_least32_t cp)
+{
+ char buf[4];
+
+ if (cp < UINT32_C(0x80)) {
+ buf[0] = (char)cp;
+ writeall(buf, 1u);
+ } else if (cp < UINT32_C(0x800)) {
+ buf[0] = (char)(( cp >> 6) | 0xC0u);
+ buf[1] = (char)(((cp >> 0) & 0x3Fu) | 0x80u);
+ writeall(buf, 2u);
+ } else if (cp < UINT32_C(0x10000)) {
+ buf[0] = (char)(( cp >> 12) | 0xE0u);
+ buf[1] = (char)(((cp >> 6) & 0x3Fu) | 0x80u);
+ buf[2] = (char)(((cp >> 0) & 0x3Fu) | 0x80u);
+ writeall(buf, 3u);
+ } else if (cp < UINT32_C(0x110000)) {
+ buf[0] = (char)(( cp >> 18) | 0xF0u);
+ buf[1] = (char)(((cp >> 12) & 0x3Fu) | 0x80u);
+ buf[2] = (char)(((cp >> 6) & 0x3Fu) | 0x80u);
+ buf[3] = (char)(((cp >> 0) & 0x3Fu) | 0x80u);
+ writeall(buf, 4u);
+ } else {
+ abort();
+ }
+}
+
+
+int
+convert(enum libcharconv_result (*conv)(const char *, size_t, size_t *, uint_least32_t *, size_t *))
+{
+ char *buf = NULL;
+ size_t bufsize = 0;
+ size_t head = 0;
+ size_t tail = 0;
+ ssize_t r;
+ enum libcharconv_result res;
+ size_t n;
+ uint_least32_t *cps;
+ size_t ncps;
+ size_t cps_size = 16u;
+ size_t i;
+
+ cps = malloc(cps_size * sizeof(*cps));
+ if (!cps) {
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(1);
+ }
+
+ for (;;) {
+ if (head == bufsize) {
+ if (tail) {
+ memmove(&buf[0], &buf[tail], head -= tail);
+ tail = 0;
+ } else {
+ buf = realloc(buf, bufsize += 8 << 10);
+ if (!buf) {
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(1);
+ }
+ }
+ }
+ r = read(STDIN_FILENO, &buf[head], bufsize - head);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: read <stdin>: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+ head += (size_t)r;
+ conv_again:
+ ncps = cps_size;
+ res = (*conv)(&buf[tail], head - tail, &n, cps, &ncps);
+ if (ncps > cps_size) {
+ cps_size = ncps;
+ cps = realloc(cps, cps_size * sizeof(*cps));
+ if (!cps) {
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(1);
+ }
+ goto conv_again;
+ }
+ switch (res) {
+ case LIBCHARCONV_NO_CONVERT:
+ writeall(&buf[tail], n);
+ tail += n;
+ if (tail != head)
+ goto conv_again;
+ break;
+ case LIBCHARCONV_CONVERTED:
+ for (i = 0u; i < ncps; i++)
+ writechar(cps[i]);
+ tail += n;
+ if (tail != head)
+ goto conv_again;
+ break;
+ case LIBCHARCONV_INDETERMINATE:
+ case LIBCHARCONV_CONVERT_IF_END:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ while (tail < head) {
+ ncps = cps_size;
+ res = (*conv)(&buf[tail], head - tail, &n, cps, &ncps);
+ if (ncps > cps_size) {
+ cps_size = ncps;
+ cps = realloc(cps, cps_size * sizeof(*cps));
+ if (!cps) {
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(1);
+ }
+ continue;
+ }
+ switch (res) {
+ case LIBCHARCONV_INDETERMINATE:
+ n = head - tail;
+ /* fall through */
+ case LIBCHARCONV_NO_CONVERT:
+ writeall(&buf[tail], n);
+ tail += n;
+ break;
+ case LIBCHARCONV_CONVERTED:
+ case LIBCHARCONV_CONVERT_IF_END:
+ for (i = 0u; i < ncps; i++)
+ writechar(cps[i]);
+ tail += n;
+ break;
+ default:
+ abort();
+ }
+ }
+
+ if (close(STDOUT_FILENO)) {
+ fprintf(stderr, "%s: write <stdout>: %s\n", argv0, strerror(errno));
+ exit(1);
+ }
+
+ free(buf);
+ free(cps);
+ return 0;
+}