Language guide
The whole language, top to bottom.
Comments & source files
Line comments start with # and run to the end of the line.
Source files use the .citril extension (or the short
.ctl). Blocks are closed with end — indentation
is never significant.
# this is a comment
print("code") # trailing comment
Values & types
| Type | Examples | Notes |
|---|---|---|
nil | nil | absence of a value |
bool | true, false | distinct from numbers |
number | 42, 3.14 | integer or float |
string | "hi" | double- or single-quoted |
list | [1, 2, 3] | mutable, reference semantics |
map | { name: "Ada" } | insertion-ordered |
function | function(x) return x end | first-class, closes over scope |
Truthiness: only nil and false
are falsy. 0 and "" are truthy.
Variables
Declare with let; reassign with =. Assigning to a
name that was never declared is an error, which keeps scoping explicit.
let x = 10
x = x + 1
x += 5 # compound: also -= *= /= //= %= ^=
Operators
Precedence, loosest to tightest:
orand==!=<<=>>=+-*///%- unary
-,not ^(power, right-associative)
+adds numbers, concatenates strings, and joins lists./is true division (always a float);//floors.and/orshort-circuit and return one operand.
Strings & interpolation
Double-quoted strings support interpolation with ${expr}; the
expression is converted with str() and spliced in. Single-quoted
strings are literal.
let name = "Ada"
print("Hi ${name}, ${2 + 2} things") # Hi Ada, 4 things
print('literal ${name}') # literal ${name}
print("a dollar: \${x}") # a dollar: ${x}
Escapes: \n \t \r \0 \\ \" \' (and \$ to escape
an interpolation).
Control flow
if cond then
...
elif other then
...
else
...
end
while cond do
...
end
for i = 1, 10 do # numeric: inclusive, optional step
...
end
for i = 10, 1, -1 do ... end
for item in collection do # a list, string, or a map's keys
...
end
break and continue work inside any loop.
Functions & closures
function add(a, b)
return a + b
end
let mul = function(a, b) return a * b end # anonymous
function make_adder(n)
return function(x) return x + n end # a closure
end
let add5 = make_adder(5)
print("${add5(10)}") # 15
A function with no return yields nil. Argument
count is checked at call time.
Indexing
let xs = [10, 20, 30]
xs[0] # 10
xs[-1] # 30 (negative indexes count from the end)
xs[0] = 99
let m = { a: 1 }
m["a"] # 1
m.a # 1 (dot is sugar for a string key)
m.b = 2 # insert
Error handling
try
risky()
error("something broke") # raise any value, usually a string
catch e
print("caught: ${e}")
end
try/catch catches both error(...) throws and
built-in runtime errors (the latter bind their message string to
e).
Enums & namespaces
Two block forms group related values under one name. Both are
frozen — their members are constants, so assigning to one is
an error. Each desugars to a frozen map, so members are read with the same
. access as any map.
An enum is a group of named constants, auto-numbered from
0; an explicit = value sets a member and unmarked
members keep counting after it:
enum Color
Red # 0
Green # 1
Blue # 2
end
enum Status
Pending = 1
Active # 2
Done # 3
end
print(Color.Green) # 1
Color.Red = 9 # error: cannot modify a frozen map
A namespace groups constants and functions. Members reach each
other through the namespace name (there is no implicit self):
namespace geometry
let tau = 6.28318
function area(r) return 3.14159 * r ^ 2 end
function circumference(r) return geometry.tau * r end
end
print(geometry.area(5)) # 78.54
For a mutable group, use a plain { } map instead.
Directives
Header lines of the form #pragma <name> [args] configure
how the runner treats a file. They are also valid comments, so they never
affect parsing.
| Directive | Effect |
|---|---|
#pragma version X.Y.Z | Refuse to run on an older Citril than required. |
See the Standard library for the built-in functions, or the Command line reference.