summaryrefslogtreecommitdiff
path: root/STYLE_GUIDE.md
diff options
context:
space:
mode:
authorAargh Rai <aargh.rai+git@gmail.com>2026-08-15 13:55:35 +0530
committerAargh Rai <aargh.rai+git@gmail.com>2026-08-15 13:55:35 +0530
commit378cc549ab442c845ac9d62cc8cef6977073fe4d (patch)
tree83b4575d7ad80cdd4f694f596f5655e164e0478f /STYLE_GUIDE.md
parent7d36403836511ab0745791ce98498cf5112633c1 (diff)
md files
Diffstat (limited to 'STYLE_GUIDE.md')
-rw-r--r--STYLE_GUIDE.md303
1 files changed, 303 insertions, 0 deletions
diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md
new file mode 100644
index 0000000..2581b5b
--- /dev/null
+++ b/STYLE_GUIDE.md
@@ -0,0 +1,303 @@
+# 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 `<stdatomic.h>` 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_<thing>(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: `<string.h>`, `<stdio.h>`, `<stdlib.h>`, `<unistd.h>`, 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.