Syntax

General Rules

Atomy is whitespace-sensitive. Operators must be surrounded by whitespace, and indentation provides hints to the parser when you don't want to use commas. For example, foo-bar is an identifier, while foo - bar is subtraction.

Requiring whitespace around operators enables the use of more symbols in identifier names, allowing pleasant conventions like question-marks (e.g. empty?) and exclamation points (e.g. do-something-destructive!) for indicating the behaviour of a method or variable.

Atomy's whitespace indentation rules are similar to Haskell's - they are not as strict as Python's, you just increase the spacing amount to indicate continuing the previous line, and continue that indentation amount to indicate continuing the current "block" of code.

Using significant whitespace, this code:

{ "hi" print
  goodbye
  2 + 2
} call

is equivalent to:

{ "hi" print, goodbye, 2 + 2 } call

With these simple line-continuing rules in place, you can spread a single chain of messages across multiple lines:

something
  foo
  sqrt

Which is parsed as:

something foo sqrt

The same rules apply to operators, which will skip any whitespace to get to the right-hand side of the expression.

foo =
  1 +
    2 *
      3

Which is parsed as:

foo = 1 + 2 * 3

Two spaces for indentation is recommended.

Comments

Atomy borrows its comment syntax from Haskell: -- for line comments, and {- -} for block comments (which can be nested).

1 -- The number, "one."
({- Blah blah blah, {- yo dawg -}, fizz buzz! -} "foo") print

Additionally, Atomy will ignore a shebang at the very start of a file.

Literals

integers

1, -1, 0xdeadbeef, 0o644, -0x10, -0o10 ...

floats

1.0, -1.5, 1.5e10, 1.4e-3, -1.4e4...

strings

"", "foo", "fizz \"buzz\""

Escape codes supported (in addition to numeric escapes):

ascii-2:
  \b \t \n \v \f \r \SO \SI \EM \FS \GS \RS \US ␣ (space)
  \BS \HT \LF \VT \FF \CR \SO \SI \EM \BS \GS \RS \US \SP

ascii-3:
  \NUL \SOH \STX \ETX \EOT \ENQ \ACK \a \DLE \DC1 \DC2
  \DC3 \DC4 \NAK \SYN \ETB \CAN \SUB \ESC \DEL
lists

[], [1], [1, .two, "three"], ...

expressions
quoted

An apostrophe (') before an expression "quotes" it, preventing evaluation and turning it into an expression literal:

Example:

'1
'a
'(1 + 1)
''(1 + b)
'{ a = 1, a + 1 }
quasiquoted

Atomy supports quasiquotation as seen in most Lisps, most similarly Clojure. A backquote (`) begins a quasiquote, inside of which you can use tilde (~) to "unquote."

These can be nested infinitely; unquoting works inside of aggregate expressions such as lists, blocks, and definitions.

Example:

> `1
1
> `(1 + ~(2 + 2))
1 + 4
> ``(1 + ~~(2 + 2))
`(1 + ~4)
> `{ a = ~(2 + 2) }
{ a = 4 }
> `[1, 2, ~(1 + 2)]
[1, 2, 3]
blocks

Blocks come in two forms. One is a simple comma-delimited list of expressions wrapped in curly braces ({ }), and another form begins with a colon (:) and optionally ends with a semicolon (;).

Block parsing is whitespace-aware; see General Rules.

Example:

> { 1 + 1 }
#<Proc:0x30920@interaction:1>
> : 1 + 1 ;
#<Proc:0x30bb0@interaction:1>
> : foo
#<Proc:0x30db4@interaction:1>

Expressions

words

foo, _foo, foo-bar, ...

Basic identifiers. They start with a lowercase letter or an underscore, followed by any number of letters, digits, underscores, or hyphens.

Example:

> true
true
> nil
nil
> _FILE
"interaction"
calls

foo(), foo(1), foo(1, 2), "foo"(1, 2), 2(3, 4) ...

A node, name, paired with a list of other nodes, the arguments.

Example:

> puts("foo")
foo
"foo"
composes

x y, foo bar(2, 3), ...

Two nodes composed side-by-side.

Example:

> 1 class
Fixnum
prefix

!foo, $foo, @foo, ...

A node prefixed by a single symbol.

Example:

> @foo
nil
> $stdin
#<IO:fd 0>
postfix

foo!, foo?, foo@, ...

A node punctuated by a single symbol at the end.

Example:

> nil?
false
binary

foo + bar, fizz * buzz, ...

Two nodes separated by an operator.

An operator can either by a string of one or more symbols, or a word defined to be an operator via infix.

Precedence and associativity can be set via infix.

Example:

> 1 + 1
2
> 1 is-in [0, 1, 1, 2, 3]
true

#language

You can tell the parser to switch to a different parser via #language foo. This will change the grammar to use the Parser defined in require("foo/language/parser").

Anatomy use this for its document files, via #language anatomy. You can change the parser at any time, even in the middle of a file. The result of the new parser is what the #language node results in.