Skip to content

Source

Files, encoding, whitespace, comments, identifiers, and statement boundaries.

Updated View as Markdown

Atoll source files use the .at extension and UTF-8 text. Source text is tokenized before parsing, so whitespace and comments can separate most tokens without changing their meaning.

const GREETING = "Hello"

fn greet(name: string): void {
    println("$GREETING, $name")
}

File structure

A file normally contains imports followed by declarations. The current declaration grammar includes constants, functions, structs, enums, errors, type aliases, traits, implementations, models, schemas, and compile-time SQL declarations.

import { Result } from std

const DEFAULT_PORT = 8080

struct Server {
    port: int
}

fn start(server: Server): void {
    println("listening on ${server.port}")
}

Module identity comes from project metadata. A source-level module declaration remains parseable for compatibility, but new project code should not repeat its directory path in every file. See Modules for project and import rules.

Whitespace

Spaces, tabs, and line breaks separate tokens. Indentation is conventional, not structural: braces delimit blocks and declaration bodies.

if ready {
    launch()
}

The style guide uses four spaces inside a block. Do not mix alignment whitespace with syntax; format a long expression over several naturally indented lines.

Statements

A newline normally terminates a statement. A semicolon is an explicit separator and is accepted where two statements need to share a line.

first := 1
second := 2; third := 3

Newlines do not terminate an expression while the parser is inside a parenthesized argument list, a collection literal, a declaration body, or a query continuation.

total := calculate(
    subtotal,
    tax,
    discount,
)

Postfix operations do not silently continue across an ordinary statement boundary. Keep a call chain visually attached to the expression it extends.

Comments

// starts a line comment. /* ... */ starts a block comment, and block comments may nest.

// One line.
value := 10

/*
    A block comment.
    /* Nested comments are valid. */
*/

/// and /** ... */ use documentation-comment spelling. The parser preserves them as documentation trivia and attaches their ranges to the following declaration for tools such as hover and documentation extraction.

/// Returns the larger input.
fn maximum(a: int, b: int): int {
    return if a > b { a } else { b }
}

Comments are not expressions and do not affect runtime behavior.

Identifiers

Identifiers name declarations, fields, parameters, locals, and labels. Ordinary program names use ASCII letters, digits, and underscores and cannot begin with a digit.

retry_count := 0
http2_enabled := true

The compiler’s naming lint expects:

  • snake_case for functions, methods, locals, parameters, and fields;
  • PascalCase for structs, enums, traits, errors, aliases, and variants;
  • SCREAMING_SNAKE_CASE for constants;
  • lowercase names for built-in scalar types.

Naming is a lint rather than a different identifier grammar. The complete table and suppression syntax are in Naming.

Keywords

Structural words such as fn, struct, enum, trait, impl, if, match, for, return, break, and continue are reserved in their grammar positions. Query words such as FROM, WHERE, and SELECT are contextual and are recognized inside integrated query expressions.

Some historical framework words remain lexer tokens even though the current parser has no declaration for the proposed feature. A reserved token is not evidence that its old design is implemented; consult Feature Status.

Blocks

Braces delimit blocks. A block contains statements and may end with a tail expression. A tail expression has no semicolon and supplies the block’s value.

fn sign(value: int): int {
    if value < 0 {
        -1
    } else if value > 0 {
        1
    } else {
        0
    }
}

Blocks and their value rules are covered in Blocks.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close