Variables must be declared before they are used.
# Declare one variable.
var a_number = 10
# Multiple variables can be declared at once.
var a_double = 1.23,
greeting = "Hello",
numbers = [1, 2, 3]
# A type can be provided as well.
var doubles: List[Double] = []
# A cast can also be used to supply a type.
var strings = [].@(List[String])
Lily uses type inference to determine a var's type when it's declared. In most cases, a type does not have to be given. The type of a variable cannot be changed.
Constants cannot be modified after they are initialized. Constants can only be
initialized with a literal. Only Double
, Integer
, and String
can be
constants.
constant magic_number = 55
constant minus_five = -5.5
constant greeting = "Hello"
# Invalid, even though the expression only involves literals
# constant count = 2 + 2
Lily has three kinds of comments. One for a line, one for a block, and one for documentation.
# This is a line comment.
# var w = 10
# This is a block comment.
var w = #[ 10 ]# 20
#[
Block comments can span multiple lines.
This is a block comment.
It can span multiple lines, but doesn't have to.
These don't support nesting.
]#
### This is a docblock.
### If you use docgen to generate documentation, it will include information in
### docblocks.
### The "docgen" section has more info on how to write docblocks.
define example
{
}
Lily comes with several data types already defined. A few of those data types have dedicated syntax to make them easier to use.
# Boolean can be true or false.
var is_open = true
var is_warm = false
Byte
ranges from 0 to 255 inclusive.
var space = ' '
# Escape codes are allowed.
var tab_byte = '\t'
var byte_127 = 0x7Ft
# You can specify a type to coerce an Integer.
var byte_a: Byte = 12
var byte_b = 12.@(Byte)
A series of bytes. Can include \0
and may have invalid utf-8. By default,
ByteString
literals only span one line.
var bytes = B"123456"
# Use triple quotes to span multiple lines.
# A ByteString can use any escape codes mentioned below.
var multiline_bytes = B"""
this
spans
multiple
lines
"""
# ByteString can be subscripted.
var byte_index = multiline_bytes[0]
A very small or very large value.
var small_double = 0.000004
var large_double = 10e1
var negative_double = -1.5
A key to value mapping. The key must be Integer
or String
. The value can be
any type. The type of a Hash
is written as Hash[key, value]
.
var number_table = [
"One" => 1,
"Two" => 2,
# A trailing comma is allowed on the last value.
"Three" => 3,
]
var table_value = number_table["Two"]
# New entries can be created by assignment.
number_table["Four"] = 4
# This raises KeyError, because the key does not exist.
# var bad_key = number_table["Twenty"]
# Specifying a type for an empty Hash.
var empty_hash: Hash[Integer, List[String]] = []
var empty_cast_hash = [].@(Hash[Integer, List[String]])
# If a Hash contains duplicates, the right-most key "wins".
# This becomes [1 => "qwerty", 2 => "hjkl"]
var duplicate_hash = [
1 => "asdf",
2 => "hjkl",
1 => "qwerty",
]
A 64-bit signed value.
var simple_integer = 12345
var negative_int = -67890
var octal = 0c744
var hex = 0xFF
var bin = 0b1010101
A container of values with the same type. The type of a List is written as
List[element type]
.
var integer_list = [1, 2, 3]
var trailing_list = [
4,
5,
# Trailing comma is optional here.
6,
]
# Indexing starts at 0.
var first_number = integer_list[0]
# Negative indexes start from the end.
var last_number = integer_list[-1]
# This raises IndexError, because the index is out of range.
# var bad_index = integer_list[20]
# Indexing by Byte is also possible.
first_number = integer_list[0t]
# Specifying a type to an empty List.
var empty_list: List[String] = []
var empty_cast_list = [].@(List[String])
A block of text that is utf-8 valid. Internally, String
is also zero
terminated.
var message = "Hello"
var multi_line = """\
This
spans
multiple
lines
"""
A List
of a fixed size, except it allows each element to be a different type.
Empty Tuple
values are not allowed.
The type of a Tuple is written as Tuple[type 1, type 2, ...]
.
var record = <[1, "left", [1]]>
# Tuple subscripts must be literals.
var record_list = record[2]
# It is a syntax error if a Tuple index is not an integer, or out of range.
# var bad_tuple_index = record[5]
The return type of a Function
that doesn't specify aFunctions that don't
specify a return type. The only instance of Unit
is unit
.
var my_unit = unit
The following are "magic" keywords. When they are invoked, they are replaced with a literal.
__dir__
is a directory relative to the first file imported.
__file__
is the name of the current file.
__function__
is the name of the current function. If outside of a definition,
this returns __main__
for the first import, or __module__
for subsequent
imports.
__line__
is the current line number.
When writing a Byte
, ByteString
, or String
, the following escape sequences
are allowed. In the case of String
, sequences that specify a value over 127 or
0 are not allowed.
\a
: Terminal bell (\007
)
\b
: Terminal backspace (\008
)
\t
: Tab (\009
)
\n
: A newline (\010
)
\r
: A carriage return (\013
)
\"
: The "
character.
\'
: The '
character.
\\
: The \
character.
\/
: \
on Windows, /
elsewhere.
\nnn
: Base 10, 0 to 255 inclusive. \0
is equivalent to \000
.
\xNN
: Hex, \x00
to \xFF
inclusive. \x0
is equivalent to \x00
.
\<newline>
: Bytestring
and String
only. The newline of the current line
and leading whitespace ( or
\t
) will be omitted.
Lily's precedence table, from lowest to highest:
Operator | Description |
---|---|
= /= *= += -= <<= >>= |
Assign/compound assign |
|| |
Logical or |
&& |
Logical and |
>= > < <= == != |
Comparison and equality |
++ |
Concatenate |
|> |
Function pipe |
& | ^ |
Bitwise and, or, xor |
<< >> |
Bitwise shifts |
+ - |
Plus, minus |
% * / |
Modulo, multiply, divide |
Operations such as x.y
member lookup and subscripts take over either the
entire current value, or the right side of the current binary operation.
The following operations are supported:
Basic arithmatic (+ - * /
): Any two Double
values, any two Integer
values,
or one Double
and one Integer
. With mixed types, the result is Double
.
Bitwise operations (<< >> %
): Only two Integer
values.
Comparison operations (>= > < <=
): Any two Byte
, Double
, Integer
, or
String
values.
Equality (== !=
) is allowed against any two values with exactly the same type.
For primitive numeric values, equality works as expected.
Hash
, List
, and Tuple
, values are equal if they're structurally equal
to each other. [1] == [1]
is true
.
Variants such as None
and Some
also use structural equality.
Some(1) == Some(1)
is true
.
All other predefined data types and user-defined classes use exact equality.
Exception("") == Exception("")
is false
. They're only equivalent to each
other if they're the same instance.
The ++
operator, String.format
, and print
all make use of built-in
interpolation. Every kind of a value can be interpolated, and has a set way of
being interpolated.
print(true) # true
print(' ') # ' '
# ByteString values have escape codes written out.
print(B"test\r\n") # test\r\n
print(1.5) # 1.5
print(stdout) # <open file at 0x--->
print(print) # <built-in function print>
# Hash order is not guaranteed.
print([1 => "2"]) # [1 => "2"]
print(12) # 12
print([1, 2, 3]) # [1, 2, 3]
print("asdf") # asdf
print("Hello") # Hello
print(<[1, "2"]>) # <[1, "2"]>
print(unit) # unit
# Variants print how they're written.
# If Option was scoped, this would say "Option.Some(1)".
print(Some(1)) # Some(1)
# None doesn't have a full type, so a cast is necessary.
# The type doesn't matter.
print(None.@(Option[Integer])) # None
# Classes always print their address.
print(Exception("")) # <Exception at 0x--->