spclib - sp.h Programming Guide
sp.h is a single-header C standard library replacement providing modern, type-safe APIs for memory management, strings, containers, IO, and cross-platform system operations.
Core Principles
Always follow these rules when using sp.h:
| Never Use | Use Instead |
|---|---|
malloc/calloc/realloc |
sp_alloc(mem, size) |
const char* |
sp_str_t (ptr+len string) |
strcmp/strlen |
sp_str_equal() / sp_str_empty() |
printf |
sp_log() / sp_print() |
memset(&obj, 0, sizeof(obj)) |
sp_mem_zero(&obj, sizeof(obj)) |
for(i=0; i<n; i++) on C arrays |
sp_carr_for() / sp_da_for() |
Quick Reference
Setup
// In ONE C file only:
#define SP_IMPLEMENTATION
#include "sp.h"
Types
s8/s16/s32/s64- signed integersu8/u16/u32/u64- unsigned integersf32/f64- floatsc8- char (UTF-8)sp_str_t- {const c8* data, u32 len} string (no null terminator required)
Memory Allocator
sp_mem_t mem = sp_mem_os_new(); // OS-backed allocator
void* ptr = sp_alloc(mem, 1024); // Allocate 1024 bytes
sp_free(mem, ptr); // Free allocation
// Scratch arena (temporary allocations):
sp_mem_arena_marker_t mark = sp_mem_begin_scratch();
sp_str_t msg = sp_fmt(sp_mem_get_scratch(), "hello {}", sp_fmt_str(name)).value;
sp_mem_end_scratch(mark); // All scratch memory freed
Zero Initialization
my_struct_t obj = sp_zero; // {0} — use for local variables
my_struct_t* p = sp_alloc(mem, sizeof(my_struct_t));
sp_mem_zero(p, sizeof(my_struct_t)); // Zero heap-allocated memory
// sp_zero_s(T) gives (T){0} for passing to functions
String Operations
sp_str_t s = sp_str_lit("hello"); // Compile-time literal (no alloc)
sp_str_t v = sp_str_view(cstr); // Zero-allocation view from C string
bool eq = sp_str_equal(a, b); // Compare
bool empty = sp_str_empty(s); // Check empty
sp_str_t copy = sp_str_copy(mem, s); // Allocated copy
Formatting & Logging
// Type-safe format strings use {} placeholders — args MUST be wrapped in sp_fmt_* macros:
sp_log("Value = {}", sp_fmt_int(x)); // log to stdout (appends newline)
sp_print("Hello {}", sp_fmt_str(name)); // print to stdout (no newline)
sp_str_t s = sp_fmt(mem, "x={}, y={}", sp_fmt_int(x), sp_fmt_int(y)).value; // format to string
sp_fatal("unrecoverable: {}", sp_fmt_int(err_code)); // log + abort
// Argument wrappers: sp_fmt_int / sp_fmt_uint / sp_fmt_float / sp_fmt_str / sp_fmt_cstr / sp_fmt_char / sp_fmt_ptr
// Style directives inside {}: {.red} {.cyan} {.bold} {.italic} — plus sp_fmt_red() etc. as args
// Spec grammar: [fill/align] [width] [.precision] [renderer] e.g. {:>10} {:.3} {:x} {.cyan}
// sp_fmt() returns sp_str_r (a Result) — access the string via .value
Dynamic Arrays
sp_da(int) arr = SP_NULLPTR; // Type is sp_da(T), NOT sp_dyn_array(T)
sp_da_push(arr, 42); // Append element
sp_da_push(arr, 99);
sp_da_for(arr, i) { // Iterate
sp_log("arr[{}] = {}", sp_fmt_uint(i), sp_fmt_int(arr[i]));
}
u64 n = sp_da_size(arr);
sp_da_free(arr); // Free the entire array
Directory Iteration
// Note: mem is the first parameter
sp_fs_for(mem, dir, it) {
sp_log("Entry: {}", sp_fmt_str(it.entry.name));
}
// Recursive directory traversal
sp_fs_for_recursive(mem, dir, it) {
sp_log("Path: {}", sp_fmt_str(it.entry.path));
}
Hash Tables
// String-keyed hash table (recommended convenience macros):
sp_str_ht(s32) map = SP_NULLPTR;
sp_str_ht_init(mem, map);
sp_str_ht_insert(map, sp_str_lit("key"), 100);
s32* val = sp_str_ht_get(map, sp_str_lit("key"));
// C-string-keyed hash table:
sp_cstr_ht(s32) cmap = SP_NULLPTR;
sp_cstr_ht_init(mem, cmap);
sp_cstr_ht_insert(cmap, "hello", 42);
// Generic hash table (for non-string key types):
sp_ht(u64, f32) ht = SP_NULLPTR;
sp_ht_init(mem, ht);
sp_ht_insert(ht, 100, 3.14f);
f32* v = sp_ht_getp(ht, 100);
File IO
// Read entire file:
sp_str_t content;
sp_err_t err = sp_io_read_file(mem, sp_str_lit("path/to/file"), &content);
// Write to file:
sp_io_file_writer_t w;
sp_io_file_writer_from_path(&w, sp_str_lit("output.txt"));
sp_io_write_str((sp_io_writer_t*)&w, sp_str_lit("hello"), SP_NULLPTR);
sp_io_file_writer_close(&w);
Error Handling
sp_try(expr); // Return if expr fails (returns sp_err_t)
sp_try_goto(expr, err, label); // goto label on failure
sp_require(ptr != SP_NULLPTR); // Return if condition false
SP_ASSERT(condition); // Assert (calls sp_assert)
sp_fatal("msg {}", sp_fmt_int(detail)); // Log and abort
Switch Statements
switch (val) {
case A: { break; }
case B: { break; }
default: { SP_UNREACHABLE_CASE(); }
}
Module Namespaces
Search references/index.md for detailed API signatures:
| Namespace | Purpose | Key Functions |
|---|---|---|
sp_str_* |
String operations | sp_str_lit, sp_str_view, sp_str_equal, sp_str_empty |
sp_cstr_* |
C string operations | sp_cstr_len, sp_cstr_equal |
sp_da / sp_da_* |
Dynamic arrays | sp_da(T), sp_da_push, sp_da_for, sp_da_size |
sp_arr |
Fixed-capacity array | sp_arr(T, N), sp_arr_init |
sp_ht / sp_str_ht / sp_cstr_ht |
Hash tables | sp_ht_insert, sp_ht_getp, sp_str_ht_init |
sp_alloc / sp_free |
Memory allocation | sp_alloc, sp_alloc_n, sp_free, sp_mem_zero |
sp_io_* |
File IO | sp_io_read_file, sp_io_write_str, sp_io_get_std_out |
sp_fs_* |
Filesystem | sp_fs_exists, sp_fs_for, sp_fs_collect |
sp_ps_* |
Processes | sp_ps_run, sp_ps_create |
sp_tm_* |
Time | sp_tm_now_epoch, sp_tm_now_point |
sp_thread_* |
Threads | sp_thread_init, sp_thread_join |
sp_mutex_* |
Mutexes | sp_mutex_init, sp_mutex_lock |
sp_env_* |
Environment | sp_env_capture, sp_env_get, sp_env_insert |
sp_tty_* / sp_sys_* |
TTY / low-level sys | sp_tty_set_mode, sp_tty_restore, sp_sys_socket_* |
sp_os_* |
Platform | sp_os_get_kind, sp_os_sleep_ms |
sp_log / sp_print / sp_fmt |
Logging/formatting | sp_log, sp_print, sp_fmt (args wrapped in sp_fmt_*) |
Common Patterns
Allocator Setup
// At program start:
sp_mem_t mem = sp_mem_os_new();
// For most allocations, use this mem handle.
// For temporary scratch allocations:
sp_mem_arena_marker_t mark = sp_mem_begin_scratch();
// ... allocations using sp_mem_get_scratch() ...
sp_mem_end_scratch(mark);
Error Propagation
sp_err_t do_work(sp_mem_t mem) {
sp_str_t content;
sp_try(sp_io_read_file(mem, sp_str_lit("data.txt"), &content));
// content is now valid
return SP_OK;
}
String Building
sp_io_dyn_mem_writer_t w;
sp_io_dyn_mem_writer_init(mem, &w);
sp_io_write_cstr((sp_io_writer_t*)&w, "Hello ", SP_NULLPTR);
sp_io_write_str((sp_io_writer_t*)&w, name, SP_NULLPTR);
sp_str_t result = sp_io_dyn_mem_writer_as_str(&w);
Reference Files
For complete API documentation with full function signatures:
references/index.md- Comprehensive API reference (auto-generated from sp.h upstream)include/sp.h- The actual single-header library source (authoritative)include/sp/*.h- Extra module headers (sp_math,sp_elf,sp_msvc,sp_prompt,sp_cli,sp_glob,sp_http,sp_macho,sp_asset,sp_test); include them with their ownSP_*_IMPLEMENTATIONmacro
Examples
Example code demonstrating sp.h usage:
| File | Description |
|---|---|
references/example/msvc.c |
MSVC compiler specific examples |
references/example/cli/palette.c |
Terminal color palette demo |
references/example/cli/prompt.c |
Interactive CLI prompt example |
references/example/freestanding/embed.c |
Embedded usage example |
Practical Tips & Pitfalls
SP_IMPLEMENTATION 的正确使用
sp.h 是单头文件库,需要在一个且仅一个 C 文件中定义 SP_IMPLEMENTATION 宏:
// 在 main.c 中:
#define SP_IMPLEMENTATION
#include "sp.h"
// 在其他文件中只需包含头文件:
#include "sp.h"
错误现象:多个 .o 文件中出现重复定义的链接错误。
Android/Termux 平台适配
在 Android/Termux 环境中,某些 POSIX 函数不可用:
posix_spawn_file_actions_addchdir_np在 Android 上缺失 解决方案:在编译时添加-DSP_PS_DISABLE禁用进程支持模块:
CFLAGS += -DSP_PS_DISABLE
API 名称的常见错误
sp_str_eq→ 正确:sp_str_equalsp_os_read_entire_file→ 正确:sp_io_read_filesp_dyn_array(T)/sp_dyn_array_push/sp_dyn_array_for→ 正确:sp_da(T)/sp_da_push/sp_da_forsp_atomic_s32_get/set/sp_atomic_ptr_get/set→ 正确:sp_atomic_s32_load/store/sp_atomic_ptr_load/store(需传内存序SP_ATOMIC_RELAXED等)sp_os_tty_enter_raw/sp_os_tty_restore/sp_os_print→ 正确:sp_tty_set_mode/sp_tty_restore/sp_io_write_str(sp_io_get_std_out(), ...)sp_env_set(name, value)→ 正确:sp_env_insert(&env, name, value)(先sp_env_capture获取 env)sp_str_map/sp_str_reduce→ 已移除("str: kill fake functional shit")
字符串结构成员
sp_str_t 结构使用 .data 成员,而不是 .ptr:
// ❌ 错误
c8 ch = str.ptr[i];
// ✅ 正确
c8 ch = str.data[i];
零初始化注意事项
sp_zero 不能用于全局变量赋值(它不是编译时常量表达式):
// ❌ 错误(可能在某些编译器上不通过)
editor_t E = sp_zero;
// ✅ 正确方式:显式初始化或使用 sp_mem_zero
editor_t E;
sp_mem_zero(&E, sizeof(E));
注意:sp_zero 在局部变量和 sp_alloc + sp_mem_zero 组合使用是安全的。
字符处理头文件
使用 isalpha、isdigit 等函数时需要包含 <ctype.h>:
#include <ctype.h> // 必须包含
文件读写 API
文件操作应使用 sp_io_* 系列函数:
- 读取:
sp_err_t err = sp_io_read_file(mem, path, &content);(注意第三个参数是输出指针) - 写入:使用
sp_io_file_writer_from_path()+sp_io_write_str()+sp_io_file_writer_close()
格式化参数必须包裹类型宏
sp.h 的格式字符串使用 {} 占位,参数必须用 sp_fmt_* 宏包裹(这是类型安全的核心):
// ❌ 错误:裸参数,编译器无法推断类型
sp_log("Value: {}", x);
// ✅ 正确:用 sp_fmt_* 宏包裹,明确类型
sp_log("Value: {}", sp_fmt_int(x));
sp_log("Name: {}, Age: {}", sp_fmt_str(name), sp_fmt_uint(age));
// sp_fmt() 返回 sp_str_r(Result 类型),取字符串要用 .value
sp_str_t s = sp_fmt(mem, "{}", sp_fmt_int(x)).value;
可用的包裹宏:sp_fmt_int / sp_fmt_uint / sp_fmt_float / sp_fmt_str / sp_fmt_cstr / sp_fmt_char / sp_fmt_ptr,以及颜色/样式宏 sp_fmt_red() / sp_fmt_cyan() / sp_fmt_bold() / sp_fmt_italic() 等。
Common Mistakes
// ❌ 错误: 使用C字符串 + printf
const char* name = "Alice";
printf("Hello %s\n", name);
// ✅ 正确: 使用sp_str_t + sp_log(参数用 sp_fmt_str 包裹)
sp_str_t name = sp_str_lit("Alice");
sp_log("Hello {}", sp_fmt_str(name));
// ❌ 错误: 手动计算字符串长度
if (strlen(str) > 0) { ... }
// ✅ 正确: 使用sp.h的API
if (!sp_str_empty(str)) { ... }
// ❌ 错误: 裸malloc
int* arr = malloc(sizeof(int) * 10);
// ✅ 正确: 使用sp_alloc (需要 sp_mem_t 句柄)
int* arr = sp_alloc(mem, sizeof(int) * 10);
// ❌ 错误: 手动for循环遍历动态数组
for (u32 i = 0; i < sp_da_size(arr); i++) { ... }
// ✅ 正确: 使用遍历宏
sp_da_for(arr, i) { ... }
// ❌ 错误: 格式化参数不包裹类型宏(裸参数)
sp_log("Value: {}", name);
// ✅ 正确: 用小写 sp_log,参数用 sp_fmt_* 宏包裹
sp_log("Value: {}", sp_fmt_str(name));
Checklist
在提交代码前,确认:
- 使用
sp_zero或sp_mem_zero()初始化结构体 - 使用
sp_str_t而不是const char* - 使用
sp_alloc(mem, size)而不是malloc()— 注意 mem 参数 - 使用
sp_log()/sp_print()而不是printf() - 使用
sp_str_empty()而不是检查len > 0 - Switch 语句处理所有枚举值,default 用
SP_UNREACHABLE_CASE() - 使用
sp_da_for()或sp_carr_for()遍历数组 - 字符串比较使用
sp_str_equal()而不是strcmp() - 格式化字符串参数用
sp_fmt_int/sp_fmt_str等宏包裹(类型安全,裸参数编译不过) - 动态数组类型使用
sp_da(T)而非sp_dyn_array(T)
Finding APIs
When looking for a specific function:
- Check the namespace table above
- Search
references/index.mdfor the pattern - Search
include/sp.hfor the exact function/macro definition (authoritative) - All public APIs are marked with
SP_APIin the source