commit e87c69019725a0c08d8a1329614ac3e519758d9e
parent 85ca53b7d3dfe775399b30f30a1531c64d6c4726
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 5 Jun 2026 19:39:59 -0700
Add freestanding runtime ctype
Diffstat:
6 files changed, 182 insertions(+), 10 deletions(-)
diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c
@@ -37,8 +37,9 @@ static const char* const kRtSrcX64[] = {
"assert/assert.c", "int/int.c",
"int/si_div.c", "fp/fp.c",
"mem/mem.c", "string/string.c",
- "stdlib/stdlib.c", "stdlib/qsort.c",
- "stdio/printf.c", "atomic/atomic_freestanding.c",
+ "ctype/ctype.c", "stdlib/stdlib.c",
+ "stdlib/qsort.c", "stdio/printf.c",
+ "atomic/atomic_freestanding.c",
"cache/clear_cache.c", "kit/ifunc_init.c",
"int64/int64.c", "coro/x86_64.c",
"coro/coro.c", "stack/backtrace.c",
@@ -62,8 +63,9 @@ static const char* const kRtSrcAarch64Elf[] = {
"assert/assert.c", "int/int.c",
"int/si_div.c", "fp/fp.c",
"mem/mem.c", "string/string.c",
- "stdlib/stdlib.c", "stdlib/qsort.c",
- "stdio/printf.c", "atomic/atomic_freestanding.c",
+ "ctype/ctype.c", "stdlib/stdlib.c",
+ "stdlib/qsort.c", "stdio/printf.c",
+ "atomic/atomic_freestanding.c",
"cache/clear_cache.c", "kit/ifunc_init.c",
"int64/int64.c", "coro/aarch64.c",
"coro/coro.c", "fp_tf/fp_tf.c",
@@ -75,8 +77,9 @@ static const char* const kRtSrcAarch64Darwin[] = {
"assert/assert.c", "int/int.c",
"int/si_div.c", "fp/fp.c",
"mem/mem.c", "string/string.c",
- "stdlib/stdlib.c", "stdlib/qsort.c",
- "stdio/printf.c", "atomic/atomic_freestanding.c",
+ "ctype/ctype.c", "stdlib/stdlib.c",
+ "stdlib/qsort.c", "stdio/printf.c",
+ "atomic/atomic_freestanding.c",
"cache/clear_cache.c", "kit/ifunc_init.c",
"int64/int64.c", "coro/aarch64.c",
"coro/coro.c", "coro/aarch64_macho.s",
@@ -99,8 +102,9 @@ static const char* const kRtSrcRv64Elf[] = {
"assert/assert.c", "int/int.c",
"int/si_div.c", "fp/fp.c",
"mem/mem.c", "string/string.c",
- "stdlib/stdlib.c", "stdlib/qsort.c",
- "stdio/printf.c", "atomic/atomic_freestanding.c",
+ "ctype/ctype.c", "stdlib/stdlib.c",
+ "stdlib/qsort.c", "stdio/printf.c",
+ "atomic/atomic_freestanding.c",
"cache/clear_cache.c", "kit/ifunc_init.c",
"int64/int64.c", "coro/riscv64.c",
"coro/coro.c", "fp_tf/fp_tf.c",
@@ -117,8 +121,9 @@ static const char* const kRtSrcRv32Elf[] = {
"assert/assert.c", "int/int.c",
"int/si_div.c", "fp/fp.c",
"mem/mem.c", "string/string.c",
- "stdlib/stdlib.c", "stdlib/qsort.c",
- "stdio/printf.c", "atomic/atomic_freestanding.c",
+ "ctype/ctype.c", "stdlib/stdlib.c",
+ "stdlib/qsort.c", "stdio/printf.c",
+ "atomic/atomic_freestanding.c",
"cache/clear_cache.c", "kit/ifunc_init.c",
"int32/int32.c", "coro/riscv32.c",
"coro/coro.c", "stack/backtrace.c",
diff --git a/mk/rt.mk b/mk/rt.mk
@@ -226,6 +226,7 @@ RT_BASE_SRCS = \
rt/lib/fp/fp.c \
rt/lib/mem/mem.c \
rt/lib/string/string.c \
+ rt/lib/ctype/ctype.c \
rt/lib/stdlib/stdlib.c \
rt/lib/stdlib/qsort.c \
rt/lib/stdio/printf.c \
diff --git a/rt/include/ctype.h b/rt/include/ctype.h
@@ -0,0 +1,20 @@
+/* ctype.h -- minimal freestanding character classification */
+#ifndef KIT_CTYPE_H
+#define KIT_CTYPE_H
+
+int isalnum(int c);
+int isalpha(int c);
+int isblank(int c);
+int iscntrl(int c);
+int isdigit(int c);
+int isgraph(int c);
+int islower(int c);
+int isprint(int c);
+int ispunct(int c);
+int isspace(int c);
+int isupper(int c);
+int isxdigit(int c);
+int tolower(int c);
+int toupper(int c);
+
+#endif
diff --git a/rt/lib/ctype/ctype.c b/rt/lib/ctype/ctype.c
@@ -0,0 +1,79 @@
+//===-- ctype.c - kit freestanding ctype primitives ----------------------===//
+//
+// SPDX-License-Identifier: 0BSD
+//===----------------------------------------------------------------------===//
+
+static unsigned int kit_ctype_byte(int c) {
+ return (unsigned int)(unsigned char)c;
+}
+
+static int kit_isupper(unsigned int c) { return c >= 'A' && c <= 'Z'; }
+
+static int kit_islower(unsigned int c) { return c >= 'a' && c <= 'z'; }
+
+__attribute__((weak)) int isdigit(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch >= '0' && ch <= '9';
+}
+
+__attribute__((weak)) int isupper(int c) {
+ return kit_isupper(kit_ctype_byte(c));
+}
+
+__attribute__((weak)) int islower(int c) {
+ return kit_islower(kit_ctype_byte(c));
+}
+
+__attribute__((weak)) int isalpha(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return kit_isupper(ch) || kit_islower(ch);
+}
+
+__attribute__((weak)) int isalnum(int c) { return isalpha(c) || isdigit(c); }
+
+__attribute__((weak)) int isblank(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch == ' ' || ch == '\t';
+}
+
+__attribute__((weak)) int iscntrl(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch <= 0x1f || ch == 0x7f;
+}
+
+__attribute__((weak)) int isgraph(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch >= 0x21 && ch <= 0x7e;
+}
+
+__attribute__((weak)) int isprint(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch >= 0x20 && ch <= 0x7e;
+}
+
+__attribute__((weak)) int ispunct(int c) { return isgraph(c) && !isalnum(c); }
+
+__attribute__((weak)) int isspace(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return ch == ' ' || (ch >= '\t' && ch <= '\r');
+}
+
+__attribute__((weak)) int isxdigit(int c) {
+ unsigned int ch = kit_ctype_byte(c);
+ return isdigit((int)ch) || (ch >= 'A' && ch <= 'F') ||
+ (ch >= 'a' && ch <= 'f');
+}
+
+__attribute__((weak)) int tolower(int c) {
+ if (c == -1) return c;
+ unsigned int ch = kit_ctype_byte(c);
+ if (kit_isupper(ch)) return (int)(ch - 'A' + 'a');
+ return c;
+}
+
+__attribute__((weak)) int toupper(int c) {
+ if (c == -1) return c;
+ unsigned int ch = kit_ctype_byte(c);
+ if (kit_islower(ch)) return (int)(ch - 'a' + 'A');
+ return c;
+}
diff --git a/test/rt/cases/ctype_runtime.c b/test/rt/cases/ctype_runtime.c
@@ -0,0 +1,56 @@
+#include <ctype.h>
+
+static int same_truth(int a, int b) { return (!!a) == (!!b); }
+
+static int check_class(int c, int alnum, int alpha, int blank, int cntrl,
+ int digit, int graph, int lower, int print, int punct,
+ int space, int upper, int xdigit) {
+ return same_truth(isalnum(c), alnum) && same_truth(isalpha(c), alpha) &&
+ same_truth(isblank(c), blank) && same_truth(iscntrl(c), cntrl) &&
+ same_truth(isdigit(c), digit) && same_truth(isgraph(c), graph) &&
+ same_truth(islower(c), lower) && same_truth(isprint(c), print) &&
+ same_truth(ispunct(c), punct) && same_truth(isspace(c), space) &&
+ same_truth(isupper(c), upper) && same_truth(isxdigit(c), xdigit);
+}
+
+static int classify_ok(void) {
+ if (!check_class('A', 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1)) return 1;
+ if (!check_class('G', 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0)) return 2;
+ if (!check_class('f', 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1)) return 3;
+ if (!check_class('x', 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0)) return 4;
+ if (!check_class('7', 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1)) return 5;
+ if (!check_class(' ', 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0)) return 6;
+ if (!check_class('\t', 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0)) return 7;
+ if (!check_class('\n', 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0)) return 8;
+ if (!check_class('\v', 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0)) return 9;
+ if (!check_class('\f', 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0)) return 10;
+ if (!check_class('\r', 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0)) return 11;
+ if (!check_class(0x7f, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0)) return 12;
+ if (!check_class('!', 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0)) return 13;
+ if (!check_class('~', 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0)) return 14;
+ if (!check_class(0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) return 15;
+ if (!check_class(-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) return 16;
+ return 0;
+}
+
+static int case_ok(void) {
+ if (tolower('A') != 'a') return 21;
+ if (tolower('Z') != 'z') return 22;
+ if (tolower('a') != 'a') return 23;
+ if (tolower('7') != '7') return 24;
+ if (tolower(-1) != -1) return 25;
+ if (toupper('a') != 'A') return 26;
+ if (toupper('z') != 'Z') return 27;
+ if (toupper('Z') != 'Z') return 28;
+ if (toupper('#') != '#') return 29;
+ if (toupper(-1) != -1) return 30;
+ return 0;
+}
+
+int test_main(void) {
+ int rc = classify_ok();
+ if (rc != 0) return rc;
+ rc = case_ok();
+ if (rc != 0) return rc;
+ return 42;
+}
diff --git a/test/rt/smoke.c b/test/rt/smoke.c
@@ -26,6 +26,7 @@
#define __ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE
#endif
+#include <ctype.h>
#include <float.h>
#include <iso646.h>
#include <kit/backtrace.h>
@@ -213,6 +214,15 @@ static int kit_atomic_ok(void) {
return 1;
}
+/* ctype: header must expose classifiers and case conversion. Compile-only --
+ runtime behavior is covered by test/rt/cases/ctype_runtime.c. */
+static int kit_ctype_compiles(void) {
+ return isalnum('A') && isalpha('z') && isblank('\t') && iscntrl('\n') &&
+ isdigit('7') && isgraph('!') && islower('q') && isprint(' ') &&
+ ispunct('#') && isspace('\r') && isupper('Q') && isxdigit('f') &&
+ tolower('Z') == 'z' && toupper('z') == 'Z';
+}
+
/* kit/backtrace: the capture/print surface compiles and resolves. Compile-only
-- smoke.c never links against a libkit_rt, so the actual walk never runs. */
static int kit_backtrace_compiles(void) {
@@ -229,5 +239,6 @@ int kit_smoke_ok(void) {
if (0) (void)kit_setjmp_compiles(0);
if (0) (void)kit_coro_compiles();
if (0) (void)kit_backtrace_compiles();
+ if (0) (void)kit_ctype_compiles();
return sum_n(3, 1, 2, 3) == 6 && kit_atomic_ok();
}