Modules

Jai_Format

moduleextensionSource

Available on macOS, Linux, Windows, Wasm

#import "Extensions/Jai_Format";

A source formatter for Jai: indentation, spacing, brace placement and blank lines. It is text in, text out, with no file access of its own. format_source formats, parse_config reads the [format] section of a jacket.toml, and format_find_config finds the right one for a file.

Example↗
text := "main::()  {\nx:=1;\n}\n";
formatted, ok, error := format_source(text);
assert(ok);
assert(formatted == "main :: () {\n    x := 1;\n}\n");

Formatting never changes the token stream. The result is lexed again and compared with the input, and on any difference format_source fails instead of returning text.

Procedures#

General#

(text: string, config: Format_Config = .{}) -> (result: string, ok: bool, error: string)

Formats Jai source. On success result is newly allocated (free it with free); on failure it is empty and error (temporary storage) says why, with a line:column: prefix when it points at the input: text that does not lex, unbalanced brackets, or the internal token check.

formatted, ok, error := format_source("f::(a:int)->int{return a;}\n");
assert(ok);
assert(formatted == "f :: (a: int) -> int {\n    return a;\n}\n");

_, ok, error = format_source("f :: () {");
assert(!ok && error.count > 0);
↗
(text: string) -> (config: Format_Config, ok: bool, error: string)

Parses the [format] section of jacket.toml text (see the docs for the keys). Other tables are skipped; without a [format] section the result is the default config. Bad values are errors, reported as line N: ...; unknown keys are dropped and listed in unknown_keys.

config, ok, error := parse_config("[format]\nindent_width = 2\nbrace_style = \"preserve\"\n");
assert(ok);
assert(config.indent_width == 2 && config.brace_style == .PRESERVE);
↗
(config: *Format_Config, assignment: string) -> (ok: bool, error: string)

Sets one setting from key=value, the value written as in the TOML file (4, true, "preserve"): how a caller that already resolved the settings passes them on. Unlike the file parser, a key the formatter does not have is an error.

config: Format_Config;
ok, error := format_apply_setting(*config, "indent_width=8");
assert(ok && config.indent_width == 8);
ok, error = format_apply_setting(*config, "colour=red");
assert(!ok);
↗
(key: string, file: string) -> string

The warning for an entry of config.unknown_keys found in the file file, with a suggestion when a known key is close.

config := parse_config("[format]\nindent_wdth = 2\n");
assert(config.unknown_keys.count == 1);
note := format_unknown_setting_note(config.unknown_keys[0], "jacket.toml");
assert(note.count > 0);
↗
(before: string, after: string) -> (ok: bool, why: string)

Whether before and after are the same program as far as the compiler can tell: the same tokens, comments and the line breaks and adjacency the parser depends on. format_source already checks its own output with this.

ok, why := format_tokens_equivalent("x:=1;", "x := 1;");
assert(ok);
ok, why = format_tokens_equivalent("x := 1;", "x := 2;");
assert(!ok && why.count > 0);
↗
(config: Format_Config, path: string) -> bool

Is path ignored by config.ignore? Globs are relative to config.root: * and ? stay within a path component, ** crosses components, and a matched directory covers its contents.

config, ok := parse_config("[format]\nignore = [\"vendor\", \"**/generated_*.jai\"]\n");
config.root = "/project";
assert(format_is_ignored(config, "/project/vendor/lib/a.jai"));
assert(format_is_ignored(config, "/project/src/deep/generated_x.jai"));
assert(!format_is_ignored(config, "/project/src/main.jai"));
↗
(directory: string, read: Format_File_Reader, stop := "") -> (config: Format_Config, file: string, ok: bool, error: string)

The settings for files in directory, from the nearest jacket.toml at or above it (up to stop), with read supplying file contents. file is the jacket.toml that was used, or "" for the defaults.

reader :: (path: string) -> string, bool {
    if path == "/project/jacket.toml" return "[format]\nindent_width = 2\n", true;
    return "", false;
}

config, file, ok := format_find_config("/project/src", reader);
assert(ok && file == "/project/jacket.toml" && config.indent_width == 2);
↗

Project#

find_configsince 0.30.0Source
(directory: string, read: Format_File_Reader, stop := "") -> (config: Format_Config, file: string, ok: bool, error: string)

The settings for files in directory: the [format] of the nearest jacket.toml in it or one of its parents, up to and including stop ("" to go to the top), else the defaults. file is the jacket.toml that was used ("" for the defaults); config.root is its directory. When the file cannot be parsed, ok is false and error says why (line N: ...).

Types#

:: enum

Where an opening brace goes.

SAME_LINE
if x\n{ becomes if x {, and }\nelse becomes } else
PRESERVE
:: struct

The formatter's settings, which are the keys of the [format] table in jacket.toml. The defaults are what you get with no config. Lines are never rewrapped; max_width only feeds format_count_long_lines.

indent_width: := 4
case_indent: := -1
case lines inside if x == {; -1: indent_width
case_body_indent: := -1
statements under a case; -1: indent_width
max_blank_lines: := 2
max_width: := 100
only reported (format_count_long_lines); lines are never rewrapped
brace_style: := Brace_Style.SAME_LINE
blank_lines_between_items: := true
a blank line around multi-line file-level items and import groups
blank_line_after_declarations: := false
in code blocks: a blank line after a run of declarations
blank_lines_around_blocks: := false
in code blocks: a blank line before and after statements with a block body
blank_line_between_cases: := false
in a switch: a blank line before a case that follows a multi-line arm
blank_line_before_comments: := false
in code blocks: a blank line above a comment that follows code
blank_line_after_guards: := false
in code blocks: a blank line after a one-line if, for or while
blank_line_before_return: := false
in code blocks: a blank line above a return
ignore: [..] string
globs relative to root, for format_is_ignored
unknown_keys: [..] string
keys the parser did not know and dropped; see format_unknown_setting_note
root: string
directory of the config file; set by whoever read it
config: Format_Config;
config.indent_width = 2;
formatted, ok := format_source("main :: () {\nx := 1;\n}\n", config);
assert(ok && formatted == "main :: () {\n  x := 1;\n}\n");
↗
Format_File_Readertypesince 0.30.0Source
:: #type (path: string) -> text: string, ok: bool

Reads the file at path for format_find_config: its text, and whether it could be read.

Constants#

:: "jacket.toml"

The config file the command-line tool looks for, upward from each file (its [format] section).