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 := 3Newlines 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 := trueThe compiler’s naming lint expects:
snake_casefor functions, methods, locals, parameters, and fields;PascalCasefor structs, enums, traits, errors, aliases, and variants;SCREAMING_SNAKE_CASEfor 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.