Command calls
A call is written without parentheses wherever the grammar allows it, and with parentheses everywhere else. This is the formatter canon and it is enforced, not preferred.
Both print("hello") and print "hello" parse. Which one to write is decided rather than left open: a call is a command
wherever the grammar allows it. torb format --check reports every file that disagrees, so this is the shape of every
snippet in this documentation and of every line in the repository.
Example#
fn checked(value: Int): Result<Int, String> {
if value < 0 {
return Fail "negative"
}
value
}
const role = "admin"
const names = ["ada", "alan"]
print "checked: {checked(1)}"
print "role: {role}, names: {names.length()}"Syntax#
<callee> <argument>, <argument> a command call
<callee>(<argument>, <argument>) a parenthesized call
<callee> <argument> { ... } a command call with a trailing closure
<callee> { ... } a call whose only argument is a trailing closureA callee is a name or a member path: print, Ok, Email.tryFrom, server.route, self.builder.add.
Rules#
A call is written as a command when all five of these hold. Otherwise it has parentheses.
It stands in command position: at the start of a statement, on the right of
=in a binding or an assignment, afterreturn, after=>, or as the default of a field of a type.The callee is a name or a member path. An index in the middle of one is part of it, because a member path with an index in it is still a member path:
mounted[index].process elapsedis the command form ofmounted[index].process(elapsed), and so isgrid[row][column].fill value.a?.bis not a member path, and neither ispair.0orload<Config>.type Cell { var value: Int var fn fill(amount: Int) { value = amount } } var grid = [[Cell(0)]] grid[0][0].fill 5 print gridAn index the path ends in is not a callee:
mounted[index] elapsedis the indexing of rule 3 and not a command, exactly asf [1]is.It has at least one argument, and the first one does not start with
(,[,-,!or..f [1]is always indexing,f -1is always subtraction, andf .Caseis always the memberf.Case. Wherefis a function, which is never subtracted from, indexed or asked for a member, the message is the call to write:Does not compilevar history: List<Int> = [] const amount = 3 history.append -amount print history // error: `history.append` is a function, and `history.append -amount` subtracts from itNo argument has an operator at its top level.
assert sum == 3would read as(assert sum) == 3, so it is writtenassert(sum == 3).The arguments are on one line. A trailing closure may go over several.
Then the rest of the rules:
A call without arguments always has parentheses. A bare name is a reference to the function, not a call of it.
const list = [1, 2, 3] print list.length()Commands do not nest. The arguments of a command are ordinary expressions, so a nested call keeps its parentheses:
Ok Some(x), neverOk Some x.print describe(numbers), neverprint describe numbers.The head of an
if,for,whileormatchis not command position. There the{is the body, so a call in the head has parentheses:if ready(now) { }.A trailing closure belongs to the outermost command call of the statement.
unless list.isEmpty() { ... }passes the closure tounless. Consequently the arguments of a command call cannot contain a trailing closure themselves:print numbers.map { _ * 2 }is an error, and the fix isprint numbers.map({ _ * 2 }).const numbers = [1, 2, 3] print numbers.map({ _ * 2 }).toList()A field is written only with
=, never with a command.port 8080is a compile error, and the fix isport = 8080- this holds everywhere, including inside a receiver closure. The one property command left is a trailing block on a field whose value is a record type, never a function:database { ... }applies the block to the value the field already holds, configuring it in place rather than replacing it. Calling a function held in a field always needs parentheses:onStart()calls it,onStart = { ... }assigns it.Parentheses always call, and never write a field.
tls(true)is an error, because aBoolfield has nothing to call - the fix istls = true.Does not compiletype Options { var tls: Bool = false } var options = Options() options.tls(true) print options.tls // error: `tls` is a field, and parentheses call a functionA multi-line
"""string is indented two spaces deeper than the line its statement starts on, with a closing"""that stands alone aligned with the content. The value does not depend on it: the lexer subtracts the indentation of the first content line from every line.fn generated(): String { const header = """ #include <stdint.h> static int64_t counted(void) """ header } print generated()Statements end at the end of the line, and there are no semicolons. A statement continues on the next line when the line ends with an operator, a comma or an open bracket, or when the next line starts with
.,?., a binary operator,withorwhere.
What this is not#
It is not a style option. torb format writes both rules over the syntax tree and torb format --check reports the
files that are not in the canon. The repository is in it, and every trb block of this documentation is checked against
it.
const role = "admin"
print "hello, {role}"print("hello")The second form parses and means the same thing. It is still wrong, because torb format rewrites it and the check fails
until it is rewritten.
A command is not a new call syntax with different semantics. f a, b and f(a, b) produce the same syntax tree apart
from a style flag. The only place where the parentheses change the meaning is a trailing block on a record field, which
is rule 10.
Whitespace never decides anything. The rules above are about tokens, not about spaces. f -1 is subtraction because
- cannot start a command argument, not because of where the space is.
A command call is not allowed inside parentheses, brackets, an operator or an argument list. Only the five positions
of rule 1 are command position, which is what keeps a b { } from being ambiguous.
const value = (print "hello")
// error: Expected `)`, found a stringRelated#
- Bindings - the right side of
=is command position. - Declaring a type - property commands and the one member namespace.
- Result - why
Ok valueandreturn Fail problemare the canonical forms. - Verify your work - the command that checks the canon.
- What a model trained on other languages gets wrong - the canon is the mistake a model makes most often.