#pragma once #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef float f32; typedef double f64; typedef size_t usize; // Clang's nullability qualifiers, compiled away elsewhere so they still document intent. // Nested because older compilers can't parse `__has_feature(...)` when it isn't defined. #ifdef __has_feature # if !__has_feature(nullability) # define _Nullable # define _Nonnull # endif #else # define _Nullable # define _Nonnull #endif // Error Codes // C23 fixed underlying type keeps it one byte, same as the old uint8_t typedef. typedef enum error : u8 { OK = 0, ERROR_REALLOCATION_FAILED, ERROR_ALLOCATION_FAILED, ERROR_ENVVAR_NOT_FOUND, ERROR_API_KEY_NOT_FOUND, ERROR_CURL_INIT_FAILED, ERROR_PRINTF_ERROR, ERROR_CURL_SLIST_APPEND_FAILED, ERROR_CURL_PERFORM_FAILED, ERROR_UNKNOWN, } error_t; // Not error codes static inline size_t max(size_t a, size_t b) { return a > b ? a : b; } #define CONCAT_(a, b) a##b #define CONCAT(a, b) CONCAT_(a, b) #define COUNTER_NAME_(name) CONCAT(name, __COUNTER__) #define RESULT(T) struct result_##T { T value; error_t error; } #define RESULT_OK(T, ...) (struct result_##T) { .value = (__VA_ARGS__), .error = OK } #define RESULT_ERR(T, ...) (struct result_##T) { .error = (__VA_ARGS__) } #define is_ok(result) ((result).error == OK) #define UNWRAP_OR_(expr, name, fallback)({ \ auto const name = expr; \ is_ok(name) ? (name).value : (fallback); \ }) #define UNWRAP_OR(expr, fallback) UNWRAP_OR_(expr, COUNTER_NAME_(_r_unwrap_or), fallback) #define TRY_OR_ELSE_(expr, r, error_name, ...) ({\ auto const r = expr; \ const error_t error_name = r.error; \ if (error_name != OK) { __VA_ARGS__; \ assert(false && "Error expression doesn't leave scope."); \ } \ r.value; \ }) #define TRY_OR_ELSE(expr, error_name, ...) TRY_OR_ELSE_(expr, COUNTER_NAME_(_r_try_or_else), error_name, __VA_ARGS__) // If condition is true, runs outexpr and goes to label. #define FAIL_IF_GOTO(cond, outexpr, label) do { if ((cond)) { (outexpr); goto label; } } while (0) // If the condition is true, sets the result-typed variable out as the specified error, and goes to the label. #define FAIL_IF_GOTO_R(cond, out, errorv, label) FAIL_IF_GOTO(cond, (out) = (typeof(out)) {.error = (errorv)}, label) // If condition is false, runs outexpr and goes to label. #define ENSURE_OR_GOTO(cond, outexpr, label) FAIL_IF_GOTO(!(cond), outexpr, label) // If condition is false, sets out (which must be a variable with a result type) to the speicifed error, and goes to label. #define ENSURE_OR_GOTO_R(cond, out, errorv, label) FAIL_IF_GOTO_R(!(cond), out, errorv, label) // Tries to get the value from the result, if it's an error, set out with an error result and goes to label. #define TRY_OR_GOTO(expr, error_name, out, label) TRY_OR_ELSE(expr, error_name, (out) = (typeof(out)){.error = (error_name)}; goto label;)