Back Home

General style & utilities

JCC uses 'LLVM C-style', where typedefs are rarely used (preferring explicit struct/union/enum) usage. Some of the conventions in this style are odd, particularly in comparison to C++, but they are used consistently throughout the project. Examples:

The two most important utility files are:

Code hygiene & Readability

Readability is certainly negatively impacted by the project being in C, but that is sort of par for the course. The formatting conventions encourage shorter variable names, but sensible names are still chosen - especially for public interfaces - and comments are used prudently to inform the reader of context.

JCC extensively uses encapsulation to keep concerns seperated. A good example is the lexer type:

struct lexer;

enum lex_create_result lexer_create(struct program *program,
                                    struct preproc *preproc,
                                    struct lexer **lexer);
void lexer_free(struct lexer **lexer);

struct lex_pos lex_get_position(struct lexer *lexer);
void lex_backtrack(struct lexer *lexer, struct lex_pos position);

void lex_peek_token(struct lexer *lexer, struct lex_token *token);
void lex_consume_token(struct lexer *lexer, struct lex_token token);

Consumers do not have access to the lexer itself, and can only interact with it via methods provided in the header. This ensures a minimal public interface for each type, as well as preventing a host of bugs related to accidentally copying or overwriting types. This is used in all places it is practical, with the notable exception being the IR format.

IR

Due to the variety of different passes that must work with it (IR builder, optimisations, lowering, register allocation, codegen), and the need for performance, the type itself is provided publically. However, there are still several measures to minimise bugs and improve general hygiene. The ir.h file provides a large set of helper methods for many simple IR operations, such as adding new operations, detaching basicblocks, and pruning the IR graph. Additionally, there is an IR validation file (validate.c(https://github.com/john-h-k/jcc/tree/main/ir/validate.c)) which is ran after every single mutating IR pass, and performs extensive validation of the IR graph. A small set of the checks it implements:

Additionally, DEBUG_ASSERT is used liberally across all IR passes to clearly show function invariants to readers and quickly catch problems, rather than slowly corrupting the IR graph.