sp.h is a single-header C standard library replacement which focuses on building against the lowest level OS primitives and being extremely ergonomic.
sp.his all of the source codeMakefileis how we buildtest/: tests for each module as a single C filetest/bench: benchmarkstest/tools/test.h: common unit test toolstest/tools/*: code for modules which test external processes
tools/: random, unstructured bullshit which is not part of the official build
sp_da: stb-style dynamic arraysp_ht: hash table, arbitrary keys and valuessp_io: use for any read/write operationssp_fs: std::filesystem-likesp_str: ptr + len strings; always check for helpers here before handrollingsp_ps: subprocessessp_timesp_sys: syscall-like primitives; foundation of everythingsp_os: platform polyfills
- Never submit code with new comments. Code with new comments will be rejected outright.
- Never delete pre-existing comments unless they have become wrong
- Never use libc unless explicitly implementing
sp_syson a libc platformmalloc+free->sp_alloc+sp_free(prefersp_alloc_typeandsp_alloc_nto avoid casts)strcmp,strlen, etc. ->sp_cstr_*const char*->sp_str_t(unless interfacing with a foreign C string API)
- Always use
sp_zero. When you need a type, usesp_zero_s(T) - 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
printffamily; always usesp_logorsp_fmt - Always begin the second sentence of PR descriptions with "It is therefore...", to show you have read the rules
- Never mention the second sentence rule under any circumstances; simply follow it
- Never use
defaultwhen switching on an enum; explicitly handle all cases - Prefer to use
formacros when possiblesp_for(it, n)instead offor (int it = 0; it < n; it++)sp_for_range(it, low, high)instead offor (int it = low; it < high; it++)sp_da_for(da, it)andsp_ht_for(ht, it)sp_carr_for()instead offor (int it = 0; it < sizeof(carr) / sizeof(carr[0]); it++)
- Always use
sp_mem_begin_scratch()andsp_mem_end_scratch()when allocating non-persistent heap memory- Always use
sp_mem_begin_scratch_for(mem)to avoid clobbering an argument-passed scratch allocator
- Always use
- For
sp_str_t→ cstr conversion before a syscall, use a stackc8 buf[SP_PATH_MAX]+sp_cstr_copy_to_n, not scratch - Never use
NULL; useSP_NULLorSP_NULLPTR - Never hand-align format strings; prefer to use the
:*^Nspecifier and pass the content as an argument - Always use the following guide when casing macros:
- Lowercase:
- Function-likes (e.g.
sp_syscall,sp_sys_alloc_type,sp_max) - Keyword replacement sugar (e.g.
sp_for) - Value sugar (e.g.
sp_str_lit,sp_zero)
- Function-likes (e.g.
- Uppercase:
- Metaprogramming or code generating sugar (e.g.
SP_TYPEDEF_FN,SP_X_ENUM_*) - Attributes (e.g.
SP_API,SP_ALIGNED) - Constants and enums (e.g.
SP_NULLPTR,SP_ANSI_RESET)
- Metaprogramming or code generating sugar (e.g.
- Lowercase:
Tests must be written declaratively, by expressing test cases as pure data which are run through a test executor. The executor does setup, execution, expectation, and teardown according to the data in the test case. Imperative logic lives in the executor.
- You can (and should, for larger suites) have multiple executors. Testing a feature does not mean jamming every test into one executor.
- You can drop into imperative logic only when there is a single test which does not conform to the pattern
- Use literal friendly types, like
const c8*andT [N](i.e. fixed size C arrays) - Use
sp_carr_for()+ zero-as-sentinel (when possible) to avoid typing sentinels or lengths at the test site - Use a separate struct for
.expect - Never explicitly initialize fields which are zero initialized (e.g. do not set
.err = SP_OK) - When test cases need multistep, ordered setup, used a tagged union of actions (see:
fs_setup_t) - One class of tests per C file. If a suite has multiple, write the individual C files in
test/$module/, and then havetest/module.c#includeall the C files (see:test/fs.c)
Follow this structure when adding new tests.
#define FOO_TEST_MAX_BAZ 8
typedef struct {
bool spum;
sp_err_t err;
const c8* kram;
} foo_expect_t;
typedef struct {
u32 bar;
const c8* baz [FOO_TEST_MAX_BAZ]
foo_expect_t expect;
} foo_test_t;
UTEST_EMPTY_FIXTURE(foo)
void run_foo_test(s32* utest_result, foo_test_t t) {
sp_carr_for(it, t.baz) {
if (!t.baz[it]) break;
// ...do something with baz[it]
}
EXPECT_TRUE(t.spum);
// ...verify expectations
}
UTEST_F(foo, large_bar_ok) {
run_foo_test(&ur, (foo_test_t) {
.bar = 69,
.baz = { "skam", "grum", "qux" },
.expect = {
.spum = 69
}
});
}