Koskari and s-expressions

Koskari is the layout language: indentation carries structure, and parentheses carry expressions. Every layout form reads to the same list structure that a parenthesized s-expression produces, so the compiler, the macro expander, and the evaluator never see which spelling was used. Layout reduces the reliance on parentheses for structure; it does not remove them.

The rules below are the whole surface. Each is shown as a pair: the layout spelling on the left, and on the right the s-expression the reader produces from it. The normative statement of the rules is Reader and layout syntax.

Lines

A line with one element is that value. A line with more than one element is a form headed by the first, which is a call, a definition, or any other form the head names.

Koskari

print "hello" name
name
def sq [x] (* x x)

s-expression

(print "hello" name)
name
(def sq [x] (* x x))

Blocks

A line ending in : opens a block. The header’s elements and the body’s lines join into one form, and each body line follows the line rule on its own. The head names a layout handler, layout/<head>; with no handler registered, the default splices body forms after the header.

Koskari

when (> n 0):
  print n
  n

s-expression

(when (> n 0) (print n) n)

Named heads and peers

Some heads have their own handler, which may gather peer blocks at the same indent and lower the whole group to one form. Block if gathers elif and else and reads as cond.

Koskari

if (< x 0):
  "negative"
elif (== x 0):
  "zero"
else:
  "positive"

s-expression

(cond
  [(< x 0) "negative"]
  [(== x 0) "zero"]
  [else: "positive"])

The handlers and their peers are catalogued in the layout handler reference.

Keywords

A line that opens with a keyword is an argument name and its value. One form after the keyword is the value; several fold to a call. A keyword followed by : takes a block as its value, and a bare k:: takes a begin block.

Koskari

print:
  "hello"
  file: stderr
  end: ""

s-expression

(print "hello" file: stderr end: "")

Parentheses inside layout

Any (, [, or { switches to s-expression rules until it closes, at any depth. A newline inside an open delimiter does not end the line, so a header argument may span lines.

Koskari

let [[a 1]
     [b 2]]:
  + a b

s-expression

(let [[a 1] [b 2]]
  (+ a b))

Side by side

Koskari

def greet [name]:
  print f"hello {name}"
  name

s-expression

(def greet [name]
  (print f"hello {name}")
  name)

Koskari

for-each [item items]:
  print item

s-expression

(for-each [item items]
  (print item))

Koskari

while (running? x):
  step x
else:
  "done"

s-expression

(while (running? x)
  (step x)
  (else: "done"))

Koskari

try:
  risky x
except [e ValueError]:
  print e
finally:
  cleanup

s-expression

(try (begin (risky x))
  [[e ValueError] (print e)]
  [finally: cleanup])

Koskari

class Point (Base):
  var x 0

s-expression

(class Point (Base)
  (begin (var x 0)))

Koskari

render:
  #[1 2 3]
  sep: ", "

s-expression

(render #[1 2 3] sep: ", ")

Where parentheses stay

Four places keep their delimiters inside layout:

  • Nested expressions, such as (< x 0) in a header.

  • Inner calls on a line, such as print (sign -3).

  • Formals and bindings, such as [name] and [[a 1] [b 2]].

  • Collection literals, such as #[1 2 3].

Statement-shaped code takes layout; expression-shaped code takes parentheses. A whole program written with parentheses only is valid Koskari, and the REPL accepts both spellings.

See also