sp.h Overview
- sp.h is a single-header C standard library replacement
- You MUST annotate references to functions from sp.h with verbatim function headers
- When providing references to code from
sp.h, you MUST provide a matching declaration from references/index.md. Function names without the full declaration are COMPLETELY useless, and WILL NOT be tolerated.
Usage
- Search
references/index.md before trying to search through the codebase. Do not guess; refer to references/index.md to find a precise search term.
Rules
- Never use
malloc, calloc, or realloc; use sp_alloc (which zero initializes)
- Unless explicitly interfacing with an existing C API, never use
const char*; use sp_str_t (pointer + length)
- Never use
strcmp, strlen, or any string.h functions with sp_str_t; use sp_str_*
- Never use
strcmp, strlen, or any string.h functions with const char*; use sp_cstr_*
- Always use
SP_ZERO_INITIALIZE(). When you need a type, use SP_ZERO_STRUCT(T)
- Always use
sp_da(T) and sp_ht(T) for dynamic arrays and hash maps (sp_dyn_array_* and sp_ht_*)
- Always use
sp_dyn_array_for(arr, it) and sp_ht_for(ht, it) to iterate sp_da and sp_ht
- Never check
str.len > 0; always use !sp_str_empty(str)
- Always use C99 designated initializers for struct literals when possible
- Always use short literal types (
s32, u8, c8, const c8*)
- Never use
printf family; always use SP_LOG()
- Always use
sp_carr_for() when iterating a C array
- Always explicitly handle all enum cases in a switch statement. Fallthroughs are OK,
default is not.
Namespaces
Use these when searching through references/index.md, references/sp.h, or references/spn.c
- Memory:
sp_alloc, sp_context, sp_allocator, sp_os
- Strings:
sp_str, sp_str_builder, sp_cstr
- Containers:
sp_dyn_array / sp_da, sp_ht, sp_rb
- IO:
sp_io
- Process:
sp_ps
- Filesystem:
sp_os
- Platform:
sp_os
- Time:
sp_tm
- Concurrency:
sp_thread, sp_mutex, sp_semaphore, sp_atomic, sp_spin_lock
- Logging:
sp_format, SP_LOG, SP_FMT_*
Common Patterns
Initialization
// Always zero-initialize structs
sp_str_builder_t builder = SP_ZERO_INITIALIZE();
sp_dynamic_array_t arr = SP_ZERO_INITIALIZE();
String Handling
// Create strings
sp_str_t literal = sp_str_lit("hello"); // Compile-time string literal
sp_str_t view = sp_str_view(some_char_ptr); // Runtime C string (calculates length)
sp_str_t copy = sp_str_from_cstr("hello"); // Allocates and copies
const char* cstr = sp_str_to_cstr(str);
Dynamic Arrays (stb-style)
sp_dyn_array(int) numbers = SP_NULLPTR;
sp_dyn_array_push(numbers, 42);
sp_dyn_array_push(numbers, 100);
sp_dyn_array_for(numbers, i) {
SP_LOG("numbers[{}] = {}", SP_FMT_U32(i), SP_FMT_S32(numbers[i]));
}
u32 count = sp_dyn_array_size(numbers);
u32 capacity = sp_dyn_array_capacity(numbers);
// Cleanup happens automatically via allocator
Hash Tables (stb-style)
sp_ht(s32, s32) hta = SP_NULLPTR;
sp_ht(sp_str_t, s32) htb = SP_NULLPTR;
sp_ht_set_fns(hta, sp_ht_on_hash_str_key, sp_ht_on_compare_str_key);
sp_ht_insert(htb, SP_LIT("answer"), 42);
s32* value_ptr = sp_ht_getp(htb, SP_LIT("answer"));
sp_ht_key_exists(htb, SP_LIT("answer"));
sp_ht_for(htb, it) {
sp_str_t* key = sp_ht_it_getkp(map, it);
s32* val = sp_ht_it_getp(map, it);
}
// Cleanup happens automatically via allocator
Formatting and Logging
// Type-safe formatting with color support
SP_LOG(
"Processing {:fg cyan} with {} {}",
SP_FMT_STR(name),
SP_FMT_U32(count),
SP_FMT_CSTR("items")
);
sp_str_t msg = sp_format("Result: {}", SP_FMT_S32(42));
// Colors: :fg, :bg, :color
// Colors: black, red, green, yellow, blue, magenta, cyan, white
// Add 'bright' prefix for bright variants
Switch Statements
// Always use braces, always handle all cases
switch (state) {
case STATE_IDLE: {
break;
}
case STATE_RUNNING: {
break;
}
default: {
SP_UNREACHABLE_CASE();
}
}
Error Handling
// Return an enum for recoverable errors (consumer app may have their own error type)
sp_err_t load_config(sp_str_t path, config_t* config) {
if (!sp_os_does_path_exist(path)) {
SP_LOG("Config not found: {}", SP_FMT_STR(path));
return SP_ERR_WHATEVER;
}
return SP_ERR_OK;
}
// Prefer to SP_ASSERT when possible
void process_array(int* arr, u32 size) {
SP_ASSERT(arr);
SP_ASSERT(size > 0);
}
// SP_FATAL is SP_LOG + SP_ASSERT(false)
if (critical_failure) {
SP_FATAL("Cannot continue: {:fg red}", SP_FMT_STR(reason));
}
1---2name: sp3description: Guide for sp.h, a single-header C standard library replacement. You must use this guide when using or discussing sp.h in any capacity.4license: MIT5---6
7# sp.h Overview
8- sp.h is a single-header C standard library replacement
9- You MUST annotate references to functions from sp.h with verbatim function headers
10- When providing references to code from `sp.h`, you MUST provide a matching declaration from `references/index.md`. Function names without the full declaration are COMPLETELY useless, and WILL NOT be tolerated.
11<example>
12user: How do I use the asset registry from sp.h?
13assistant: [Reads the index, uses the Task tool to search through sources bundled with skill (sp.h, spn.c), includes "sp_str_t sp_str_sub(sp_str_t str, s32 index, s32 len)" in answer]
14</example>
15<example>
16user: Write a function that reads a file and logs its contends
17assistant: [Searches through bundled source code with Task tool to find relevant APIs and writes function]
18</example>
19- NEVER, EVER MODIFY THE REFERENCE CODE
20
21## Usage
22- Search `references/index.md` before trying to search through the codebase. Do not guess; refer to `references/index.md` to find a precise search term.
23<example>
24user: How do I read a file in sp.h?
25assistant: [Reads index.md, searches through sp.h and spn.c with Task tool, provides concise, annotated answer]
26</example>
27- Search through `references/sp.h` judiciously as needed; do not guess symbol names, function signatures, or implementation details. Read the source code.
28- Function signatures are prefixed with `SP_API`
29- Types are prefixed with `sp_` and suffixed with `_t`
30
31## Rules
32- Never use `malloc`, `calloc`, or `realloc`; use `sp_alloc` (which zero initializes)
33- Unless explicitly interfacing with an existing C API, never use `const char*`; use `sp_str_t` (pointer + length)
34- Never use `strcmp`, `strlen`, or any `string.h` functions with `sp_str_t`; use `sp_str_*`
35- Never use `strcmp`, `strlen`, or any `string.h` functions with `const char*`; use `sp_cstr_*`
36- Always use `SP_ZERO_INITIALIZE()`. When you need a type, use `SP_ZERO_STRUCT(T)`
37- Always use `sp_da(T)` and `sp_ht(T)` for dynamic arrays and hash maps (`sp_dyn_array_*` and `sp_ht_*`)
38- Always use `sp_dyn_array_for(arr, it)` and `sp_ht_for(ht, it)` to iterate sp_da and sp_ht
39- Never check `str.len > 0`; always use `!sp_str_empty(str)`
40- Always use C99 designated initializers for struct literals when possible
41- Always use short literal types (`s32`, `u8`, `c8`, `const c8*`)
42- Never use `printf` family; always use `SP_LOG()`
43- Always use `sp_carr_for()` when iterating a C array
44- Always explicitly handle all enum cases in a switch statement. Fallthroughs are OK, `default` is not.
45
46## Namespaces
47Use these when searching through `references/index.md`, `references/sp.h`, or `references/spn.c`
48- Memory: `sp_alloc`, `sp_context`, `sp_allocator`, `sp_os`
49- Strings: `sp_str`, `sp_str_builder`, `sp_cstr`
50- Containers: `sp_dyn_array` / `sp_da`, `sp_ht`, `sp_rb`
51- IO: `sp_io`
52- Process: `sp_ps`
53- Filesystem: `sp_os`
54- Platform: `sp_os`
55- Time: `sp_tm`
56- Concurrency: `sp_thread`, `sp_mutex`, `sp_semaphore`, `sp_atomic`, `sp_spin_lock`
57- Logging: `sp_format`, `SP_LOG`, `SP_FMT_*`
58
59## Common Patterns
60### Initialization
61```c
62// Always zero-initialize structs
63sp_str_builder_t builder = SP_ZERO_INITIALIZE();
64sp_dynamic_array_t arr = SP_ZERO_INITIALIZE();
65```
66
67### String Handling
68```c
69// Create strings
70sp_str_t literal = sp_str_lit("hello"); // Compile-time string literal
71sp_str_t view = sp_str_view(some_char_ptr); // Runtime C string (calculates length)
72sp_str_t copy = sp_str_from_cstr("hello"); // Allocates and copies
73const char* cstr = sp_str_to_cstr(str);
74```
75
76### Dynamic Arrays (stb-style)
77```c
78sp_dyn_array(int) numbers = SP_NULLPTR;
79sp_dyn_array_push(numbers, 42);
80sp_dyn_array_push(numbers, 100);
81
82sp_dyn_array_for(numbers, i) {
83 SP_LOG("numbers[{}] = {}", SP_FMT_U32(i), SP_FMT_S32(numbers[i]));
84}
85
86u32 count = sp_dyn_array_size(numbers);
87u32 capacity = sp_dyn_array_capacity(numbers);
88
89// Cleanup happens automatically via allocator
90```
91
92### Hash Tables (stb-style)
93```c
94sp_ht(s32, s32) hta = SP_NULLPTR;
95sp_ht(sp_str_t, s32) htb = SP_NULLPTR;
96sp_ht_set_fns(hta, sp_ht_on_hash_str_key, sp_ht_on_compare_str_key);
97
98sp_ht_insert(htb, SP_LIT("answer"), 42);
99
100s32* value_ptr = sp_ht_getp(htb, SP_LIT("answer"));
101
102sp_ht_key_exists(htb, SP_LIT("answer"));
103
104sp_ht_for(htb, it) {
105 sp_str_t* key = sp_ht_it_getkp(map, it);
106 s32* val = sp_ht_it_getp(map, it);
107}
108
109// Cleanup happens automatically via allocator
110```
111
112### Formatting and Logging
113```c
114// Type-safe formatting with color support
115SP_LOG(
116 "Processing {:fg cyan} with {} {}",
117 SP_FMT_STR(name),
118 SP_FMT_U32(count),
119 SP_FMT_CSTR("items")
120);
121
122sp_str_t msg = sp_format("Result: {}", SP_FMT_S32(42));
123
124// Colors: :fg, :bg, :color
125// Colors: black, red, green, yellow, blue, magenta, cyan, white
126// Add 'bright' prefix for bright variants
127```
128
129### Switch Statements
130```c
131// Always use braces, always handle all cases
132switch (state) {
133 case STATE_IDLE: {
134 break;
135 }
136 case STATE_RUNNING: {
137 break;
138 }
139 default: {
140 SP_UNREACHABLE_CASE();
141 }
142}
143```
144
145### Error Handling
146```c
147// Return an enum for recoverable errors (consumer app may have their own error type)
148sp_err_t load_config(sp_str_t path, config_t* config) {
149 if (!sp_os_does_path_exist(path)) {
150 SP_LOG("Config not found: {}", SP_FMT_STR(path));
151 return SP_ERR_WHATEVER;
152 }
153
154 return SP_ERR_OK;
155}
156
157// Prefer to SP_ASSERT when possible
158void process_array(int* arr, u32 size) {
159 SP_ASSERT(arr);
160 SP_ASSERT(size > 0);
161}
162
163// SP_FATAL is SP_LOG + SP_ASSERT(false)
164if (critical_failure) {
165 SP_FATAL("Cannot continue: {:fg red}", SP_FMT_STR(reason));
166}
167```