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, thearguments
.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.