# Gacrux C Style Guide This document describes the coding conventions used throughout the Gacrux chess engine. It is derived directly from the existing source so that new code matches the established style. ## Files & Compilation Units ### Aggregation via `#include "*.c"` Multiple `.c` files are aggregated into a single translation unit by `#include`-ing them, rather than compiling separately: - `src/engine.c` pulls in `engine/threads.c`, `engine/thread.c`, `engine/moves.c`, `engine/ttable.c`. - `src/ipc.c` pulls in `ipc/go_args.c`, `ipc/moves.c`, `ipc/engine_message.c`, `ipc/state.c`, `ipc/threads.c`. - `src/main.c` and `tests/main.c` append `#include "fen.c"`, `#include "bitboard.c"`, `#include "random.c"` at the **bottom** of the file, after `main()`. This means each leaf `.c` (e.g. `src/fen.c`, `src/random.c`) is a fragment compiled only when included, never standalone. ### Internal-define gating Headers expose internal-only declarations behind a `_INTERNAL` guard. The consuming `.c` file `#define`s the sentinel before including the header: ```c #define ENGINE_THREADS_INTERNAL #include "engine/threads.h" ``` Other gates in use: - `MOVES_INTERNAL` — `include/engine/moves.h` (`__forloop_*` declarations). - `IPC_INTERNAL` — `include/ipc/state.h`. ### Test gating `#ifdef TEST_MOD` blocks at the bottom of source files define `test_*` functions and pull in any extras (e.g. `#include "fen.h"`, `#include "bitboard.c"`). The test runner (`tests/main.c`) defines `TEST_MOD` first so all tests are visible. ## Indentation & Whitespace - **2 spaces** per indent level. No tabs. - No trailing whitespace. - A single blank line separates most statements; dense blocks omit blank lines between related short statements. - Long function signatures wrap one parameter per line, indented 2 spaces, with the opening `{` on its own line: ```c void option_setting_combo( option_setting_t *target, char *option_name, char **combinations, int combinations_count, int default_index, combo_t *combo ) { ... } ``` When the signature fits, the `{` stays on the same line: ```c int main(int argc, char** argv) { ... } ``` ## Braces - Opening brace on the same line as `if`/`else`/`for`/`while` when the condition fits, or on its own line after a multi-line condition. - `else` on the same line as the closing brace: `} else if (...)`. - Single-statement bodies often omit braces: ```c if (i < MAX_TOKEN_SIZE) cmd->root[i] = 0; ``` ```c for (int j = 0; m; j++) { if (i & (1 << j)) { occs[i] |= (bitboard_t)1 << __builtin_ctzll(m); } m &= m - 1; } ``` ## Naming | Kind | Convention | Example | |-----------------|------------------------|-------------------------------------------| | Functions | `snake_case` | `get_pawn_moves`, `position_make_move` | | Typedef structs | `name_t` | `position_t`, `move_t`, `ipc_state_t` | | Named structs | `struct name` | `struct threads`, `struct child_thread_args` | | Enums/constants | anonymous `enum { }` | `enum { WHITE_KING, ..., PIECE_TYPE_COUNT }` | | Enum values | `UPPER_SNAKE_CASE` | `WHITE_SHORT_CASTLE`, `BLACK_TURN` | | Macros/constants| `UPPER_SNAKE_CASE` | `BITMASK_RANK_1`, `TT_BUCKET_SIZE` | | Private funcs | `_single_prefix` | `_add_move`, `_add_ipc_move` | | Internal funcs | `__double_prefix` | `__ttable_insert`, `__threads_args_init` | | File-scope vars | `static` | `static magic_t rook_table[64];` | ### Struct vs. typedef choice - Use `typedef struct { ... } name_t;` (anonymous) for opaque/value types that are only ever referenced through the typedef — `position_t`, `move_t`, `move_t`, `ipc_state_t`, `tentry_t`, `magic_t`. - Use `struct name { ... };` (named, no typedef) when the type must be identified by name (forward declarations, self-reference, or union members) — `struct engine_message`, `struct threads`, `struct child_thread_args`. ### Self-referential structs ```c typedef struct engine_message { atomic_int ready; ... struct engine_message *next; } engine_message_t; ``` ## Function Lifecycle Convention Every "object" type follows an `init` / `deinit` pair. - `init` operating on a caller-owned struct takes a pointer: `void xxx_init(xxx_t *out)` / `void xxx_init(struct xxx *handler)`. - `deinit` frees internal resources and takes a pointer (or by-value handle for trivially-copyable types): `void xxx_deinit(xxx_t out)`, `void uci_cmd_deinit(uci_cmd_t cmd)`. ## Macros ### Expression macros (no statement semantics) Written without surrounding parentheses, relying on call-site precedence — notably the bitmask aggregates: ```c #define whites(position) \ position.bitboards[WHITE_KING] | \ position.bitboards[WHITE_QUEEN] | \ ... ``` Single-expression macros may be parenthesized: ```c #define occupied_by(bitboard, index) (bitboard & ((bitboard_t)1 << index)) ``` ### Variadic wrapper macros A thin macro delegates to an underscore-prefixed function, building a designated-initializer struct inline: ```c #define add_move(moves, from, to, ...) _add_move(\ moves, \ from, \ to, \ (struct add_move_params) { .flags = 0, __VA_ARGS__ }\ ) ``` ```c #define ttable_insert(ttable, ...) __ttable_insert( \ ttable, \ (struct ttable_insert_args) { __VA_ARGS__ } \ ) ``` ### Statement macros Multi-statement macros use `do { ... } while (0)` (the build system does this): ```c #define RUN_CMD(...) do { \ if (fork() == 0) { \ char* args[] = __VA_ARGS__; \ ... \ } else { \ wait(&status); \ } \ } while (0); ``` ## Function signatures & parameter style - Pointer-to-struct receiver is the first parameter: `void get_king_moves(moves_t *moves, position_t position)`. - Small structs/positional params passed by value: `move_t move`, `position_t position`, `bitboard_t occ`. - `int`/pointer params are not typedef'd; use `u8`/`u16`/`u32`/`u64` (from `include/ints.h`) for widths. - `const`-ness is not heavily used; raw pointers are common even for read-only access. ## Enums Anonymous enums for constant sets: ```c enum { WHITE_KING, WHITE_QUEEN, ..., PIECE_TYPE_COUNT }; ``` ```c enum { TT_EXACT, TT_LOWERBOUND, TT_UPPERBOUND }; ``` Flag bits use explicit bit shifts: ```c enum { WHITE_SHORT_CASTLE = 1 << 0, WHITE_LONG_CASTLE = 1 << 1, ... }; ``` ## Error Handling - `assert(condition)` for internal invariants and unreachable code: `assert(0 && "Failed to find magic number")`, `assert(0 && "Invalid argument...")` - `bool`-returning predicates for validity checks: `bool check_valid_position(position_t position)`, returns `true`/`false`. - Lookup functions return `NULL` on miss: `tentry_t *ttable_find(...)`. - Error-as-data via a `bool failed` struct field: `struct fen_load { bool failed; position_t position; };` ## Comments - `//` for inline and short comments. - `/* ... */` for multi-line explanations. - Trailing inline comments often document field semantics, sometimes aligned: `i16 score; // depth analysed` - Explanatory comments about timing constraints appear in the UCI layer: `// TIMEOUT NOTICE: THE FOLLOWING BLOCK SHOULDN'T TAKE MORE THAN 5 SECONDS` ## Atomics Use `` primitives directly: - `atomic_int`, `atomic_uint_fast64_t`, `atomic_flag`. - `atomic_init(&x, v)`, `atomic_load(&x)`, `atomic_store(&x, v)`. - `atomic_flag_test_and_set(&flag)` / `atomic_flag_clear(&flag)` for spinlocks. ## Test Functions Embedded test functions: - Signature: `bool test_(void)`. - Return `true` = pass, `false` = fail. - Use `assert()` for intermediate checks; `return false` to fail a specific assertion so the runner can report it. ```c bool test_uci_cmd_t() { uci_cmd_t cmd; uci_cmd_init(&cmd); uci_cmd_add(&cmd, "john"); uci_cmd_add(&cmd, "debug"); uci_cmd_add(&cmd, "on"); if (strncmp(cmd.root, "debug", MAX_TOKEN_SIZE) != 0) { uci_cmd_deinit(cmd); return false; } ... uci_cmd_deinit(cmd); return true; } ``` ## Build system (`build.c`) - A self-rebuilding `nob.h`-style driver. - Defines `CC` macro: `#define CC "gcc", "-fsanitize=address", "-g", GLOBAL_INCLUDE` - `needs_rebuild(bin, src)` compares mtimes via `stat()`. - Subprocesses launched via `fork()`/`execvp()` inside a `RUN_CMD` `do/while(0)` macro, with `wait(&status)`. - Output object files live in `output/`. ## Include ordering The canonical order (see `src/uci.c`, `src/main.c`) is: 1. System headers: ``, ``, ``, ``, etc. 2. Blank line. 3. Project headers using path-qualified includes: `"uci/internals/command.h"`, `"ipc/state.h"`, `"engine/magic.h"`. Some older files (`src/bitboard.c`, `src/engine/moves.c`) place the project header first or a system header last; prefer the system-first / blank-line / project order for new files.