Source Style and Formatting
This file is part of the Nocter language specification. The specification entry point is README.md.
Direction
Adopted: Nocter syntax should be tolerant where whitespace is not semantically meaningful, while the formatter defines one official source style.
Rules:
- Formatting must not change program semantics.
- Style violations are not compile errors in v0.
- The parser accepts valid whitespace variations where tokenization remains unambiguous.
- The formatter emits the official source style.
- Specification examples,
README.mdexamples,spec/guides/ai.md,example.nct, andspec/examples/valid/should use formatter output as the canonical presentation. - The formatter belongs in the compiler toolchain, not in editor extensions.
This keeps the language pleasant to write by hand while avoiding multiple competing styles in documentation, generated examples, diagnostics, AI-generated code, and future editor tooling.
CLI
Formatting is exposed through nocter fmt.
nocter fmt app.nct
nocter fmt --check app.nct
Rules:
fmttakes one.nctsource file in v0.fmtformats only the file named on the command line.fmtdoes not follow imports.fmtdoes not treat the input as a compile-unit root.fmtdoes not perform name resolution, type checking, ownership checking, target lowering, code generation, or execution.fmtmust not delete or rewrite comments.- Until comment-preserving formatting is implemented,
fmtmust reject files that contain comments instead of rewriting them. - If parsing fails,
fmtmust not rewrite the file. fmtrewrites the file in place only after formatting succeeds.fmt --checkcompares the input against formatter output and does not rewrite the file.fmt --checkexits successfully only when the file already matches formatter output.fmtdoes not need a target option.
Project-wide formatting is deferred until package roots or manifests exist.
Indentation and Blocks
Rules:
- Formatter output uses 4 spaces per indentation level.
- Formatter output does not use tabs for indentation.
- Opening braces stay on the same line as the construct that owns the block.
elsestays on the same line as the previous closing brace.- Top-level declarations are separated by one blank line.
- Empty blocks may stay as
{}only when the construct is intentionally empty and readability is not harmed.
Examples:
if path.len() == 0 {
return Error.new("app.missing_path", "missing path")
} else {
use(path)
}
impl File {
pub method &+self.write_text(text: &str): void! {
...
}
}
Type Syntax Spacing
Rules:
- Fallible type syntax is formatted as
T!. - Fallible optional success is formatted as
T?!. - Optional type syntax is attached to the success type:
T?. - Pointer and borrow syntax is attached to the type:
*T,&T,&+T. - Generic type arguments are attached to the type name:
Buffer<T>. - Unsized array data type syntax is formatted as
[T]; array slices are formatted as&[T]and&+[T]. - Fixed-size arrays are formatted as
[T; N]. - Parentheses used for type grouping do not add internal padding:
(T)!.
Examples:
func open(path: &str): File!
func env(name: &str): &str?!
func first(items: &[&str]): &str?
Formatter output uses T?! for fallible optional success values because the postfix operators can be read in order: optional success first, fallible wrapper second.
Declarations
Rules:
- Type annotations use no space before
:and one space after:. - Function return annotations use no space before
:and one space after:. - Commas have no preceding space and one following space on a single line.
- Multi-line parameter lists put one parameter per line and keep a trailing comma.
- The closing
)of a multi-line parameter list is aligned with the start of the declaration.
Examples:
let count: u64 = 0
let home = maybe_home otherwise { return none }
func read_all(
allocator: &+Allocator,
path: &str,
): String! {
...
}
Expressions
Rules:
- Assignment and compound assignment use spaces around the operator.
- Binary operators use spaces around the operator.
otherwisefallback blocks are formatted asvalue otherwise { body }.ifandmatchexpressions use the same formatting as their statement forms.- A result-only body may be formatted as
{ expr }when it is short. - Unary operators are attached to their operand.
- Function calls have no space between callee and
(. - Method calls have no space around
.. - Indexing has no space between value and
[. - Field access has no space around
..
Examples:
count += 1
let home = maybe_home otherwise { "/tmp" }
let label = if is_ready { "ready" } else { "waiting" }
let byte = bytes[i]
file.write_text("hello")
Control Flow
Rules:
- Control-flow keywords are followed by one space before their condition or pattern.
catchafter a fallible expression is separated from the expression by one space.- Single-pattern enum checks use
if expr is Pattern { ... }. matcharms usePattern { ... }.matchfallback arms use_ { ... }and must be written last.- Range
forsyntax is formatted asfor i in start..<end { ... }.
Examples:
let file = File.open(path) catch error {
return Error.new("std.io.open_failed", error.message)
}
match error {
AppError.missing_path {
...
}
AppError.open_failed(path) {
...
}
_ {
...
}
}
return match error {
AppError.missing_path { missing_code() }
AppError.open_failed(path) { code_for(path) }
_ { unknown_code() }
}
for i in 0..<bytes.len() {
...
}
Comments
Rules:
- Formatter output must preserve normal comments and doc comments once comment-preserving formatting is implemented.
- Current formatter v0 rejects files that contain comments with a diagnostic instead of rewriting them.
- Formatter output may adjust surrounding whitespace but must not rewrite comment text.
- Line comments keep at least one space between code and
//when they share a line. - Block comments keep their internal text unchanged in v0.
- Doc comments keep their doc marker spelling:
///,/**,//!, or/*!.
Comment paragraph reflow is not part of v0.
Non-Goals in v0
The following are not part of formatter v0:
- project-wide formatting
- import sorting
- semantic rewrites
- line-length based wrapping guarantees
- configurable style profiles
- editor-only formatting rules
- comment paragraph reflow