Coding Style
Preface
This is my personal style guide for Jai. This site is more a reference for myself and for people who work on my code.
Inspired by suckless.org.
Index
Show
File Layout
Jai allows the usage of
#scope_file,
#scope_module, and
#scope_export.
The ugly pattern is, that some people switch between file-scope and module-scope,
which looks (to me) chaotic and forces your brain to do extra work.
This is why in my code #scope_file appears only once per file
and #scope_module only exists, if I have to use
#scope_export.
To keep the separation even cleaner, a general structure (GS) is maintained per scope, not per file.
This is the general structure:
- Variables
- Constants
- Enums
- Unions
- Structs
- main procedure (if applicable)
- procedures
And this is how a file is structured:
- General comments / documentation
- #scope_export
- GS
- #scope_module
- GS
- #scope_file
- GS
- Imports
- License
Here are some examples:
Ex. 1, commonly used
server_offline: bool;
thread_group: Thread_Group;
DIR_WWW :: "./www";
DIR_TEMPLATES :: "./templates";
Export_Kind :: enum {
NONE;
HTML;
PDF;
TXT;
}
Work_Data :: struct {
item: *Item;
page: *Page;
}
main :: () {
/** snip */
}
path_remove_match :: (match: string, path: string) -> string {
/** snip */
}
#scope_file
work_data: Work_Data;
last_error: int;
MAX_CPUS :: 200;
THREAD_LOGGING_ENABLED :: false;
Item :: struct {
/** snip */
}
Page :: struct {
/** snip */
}
dir_swap_atomic :: (dir_temp: string, dir_live: string) -> ok: bool {
/** snip */
}
has_hyper_threading :: () -> bool {
/** snip */
}
#import "Basic";
#import "Thread";
#import "System";
#import "Machine_X64";
#import "stringpad";
#import "htmltemplate";
#load "../shared.jai";
#load "../config.jai";
#load "magic.jai";
Ex. 2, export procedures
/** Some explaination / documentation about the public APIs */
#scope_export
important_handle: s32;
another_handle: s32;
api_something :: (s: *u8, len: u64) -> bool {
/** snip */
}
api_something2 :: (handle: s32, proc: *void) -> s32 {
/** snip */
}
#scope_module
foo: string;
bazz: int;
Bar :: enum {
YAY;
NAY;
}
#scope_file
dir_swap_atomic :: (dir_temp: string, dir_live: string) -> ok: bool {
/** snip */
}
has_hyper_threading :: () -> bool {
/** snip */
}
#import "Basic";
Comments
If a comment is some sort of documentation use /** Important things */.
If it's a short note or todo use // Note(Bob): Don't forget this stuff here.
If you need to visually separate sections, use:
/* ------------- *
* Path Routines *
* ------------- */
The usage of comments should be sparse. Only two types of comments are allowed:
- Apology letter
- Explaining why $thing exists
Avoid documenting obvious things like this:
/** This procedure creates an axis-aligned bounding box, based on a rectangle.
@param rect: Takes an `Rectangle` type
@return: An axis-aligned bounding box
@errors: none
*/
make_aabb :: (rect: Rectangle) -> Aabb {
a: Aabb;
a.upper_bound = { rect.x, rect.y };
a.lower_bound = { rect.x + rect.width , rect.y + rect.height };
return a;
}
The name, input, and return type already indicates what is happening.
This is better:
/** DFS. Favors the left side. Callback can decide how to proceed with the tree-walking */
tree_visit :: (
tree: *Tree,
user_data: $T,
proc: (info: Tree_Visitor_Info, user_data: T) -> Tree_Visit_State,
start_index := -1
)
-> ok: bool
{
/** snip */
}
Line Length
Keep it to max 94 characters (arbitrary chosen, no deeper reasoning).
Block Formatting
Some procedures will hit the line limit, so this is how I break it up:
/** Pretty long proc */
foo_bar_looong_name :: (
thing1: string,
thing2: int,
thing3: (int) -> (bool),
thing4: bool
)
-> (ok: bool, idx: int)
{
/** snip */
}
/** When there are only two or three simple params, I let them in one line */
foo_bar_thing_but_also_a_long_name :: (
some_param: string, another_param: int,
) {
/** snip */
}
Macros
If possible, macros should be used in the same scope, where they're needed.
For example, if you have a lexer procedure, put the traversal
macros inside it.
The reason for that is locality of behaviour.
It allows you to understand the code quicker, because you don't have to traverse so much around the codebase and switch mental context.
format_date :: (time: Apollo_Time, fmt: string) -> string {
is_at_end :: () -> bool #expand {}
pop :: () -> u8 #expand {}
peek :: () -> u8 #expand {}
parse_fmt :: (idx: int, cal: Calendar_Time, fmt: string) -> string {}
while !is_at_end() {
/** snip */
}
}
Tabs vs Spaces & Width
I personally prefer spaces. But this is because I got Python-brain-damaged. There's no deeper reason for it.
And regarding the width: at least four characters. Anything below is just exhausting to read after hours of coding.
Naming
How to name things is adapted from Jai:
- Variables: snake_case
- Constants: SNAKE_CASE
- Procedures/macros: snake_case
- Enums, Unions, Structs: Pascal_Snake_Case
Jai allows to break up variable names like this:
my_\ _variable_name: string;
my_amazing_variable_name: string;
Which is a neat feature, but then you can delete grep. So please avoid it.
Namespaces
Currently I don't really use namespaces, I prefix procedures with a specific name.
Like tree_add() and tree_remove(). And shared or helper
procedures are named without a prefix and below #scope_file.
tree_add :: () {
/** snip */
traverse();
}
tree_remove :: () {
/** snip */
traverse();
}
#scope_file
traverse :: () {}
I tried to use namespaces, but then this pattern, Foo.thing(), adds so
much noise.
I'm aware that I could use using Foo;, but this will also add line-noise.
make_something_cool :: (
group: *Th.Thread_Group,
thread: *Th.Thread,
work: *void
)
-> Th.Thread_Continue_Status
{
/** snip */
}
Just prefixing a name and go with pascal_case makes the code more uniform - at least for my eyes.
Switch
Use this indentation:
if foo == {
case .ONE;
do_thing();
case .TWO;
do_other_thing();
case;
log("Something");
}
If the code is actually simple like the example above:
if foo == {
case .ONE; do_thing();
case .TWO; do_other_thing();
case;
log("Something");
}