Lily's block syntax is a unique blend with influences from C, Python, Ruby, and Rust. The most important parts are:
No semicolons. Lily can work out automatically when one expression stopts and another starts. This allows expressions to span multiple lines.
Lily uses curly braces to denote the scope of a block. Unlike most curly brace languages, Lily uses one set of curly braces to cover a whole block. In practice, it looks like this:
Here's an example in C.
int abc, def, ghi, jkl;
if (a == 1) {
abc = 2;
def = 3;
}
else: {
ghi = 4;
jkl = 5;
}
The same example in Lily.
var a = 1, abc = 0, def = 0, ghi = 0, jkl = 0
if a == 1: {
abc = 2
def = 3
else:
ghi = 4
jkl = 5
}
Lily allows several types to be checked for truthiness. Here are the different types and what Lily considers false for them.
Type | False value |
---|---|
Boolean |
false :P |
Double |
0.0 |
Integer |
0 |
List |
[] |
String |
"" |
Conditional execution of code.
define letter_for_grade(grade: Integer): String
{
if grade >= 90: {
return "A"
elif grade >= 80:
return "B"
elif grade >= 70:
return "C"
elif grade >= 60:
return "D"
else:
return "F"
}
}
Custom scope for a block of code.
{
var v = 10
print(v) # 10
}
{
var v = "hello"
print(v) # hello
}
Execute a block of code as long as a condition is met.
{
var values: List[Integer] = []
var i = 0
do: {
values.push(i)
} while i
print(values) # [0]
}
{
var values: List[Integer] = []
var i = 0
# do supports break and continue
do: {
i += 1
if i % 2: {
continue
elif i == 4:
break
else:
values.push(i)
}
} while i < 5
print(values) # [2]
}
{
# Variables declared in a do cannot be used for the condition.
#[
values = []
i = 0
do: {
if 1: {
continue
}
var v = 10
} while v == 10
]#
}
A for
loop takes either a range, or a List
.
The range style looks like this: for index in start...end (by iter)
. The part
in parentheses is not required.
{
var values: List[Integer] = []
# Default increment is 1
for i in 0...5: {
values.push(i)
}
# Syntax error, because i is restricted to the for loop's scope..
# print(i)
print(values) # [0, 1, 2, 3, 4, 5]
}
{
var values: List[Integer] = []
# An existing local can be used for the increment.
var index = 0
# A custom increment can be specified.
for index in 0...10 by 2: {
values.push(index)
}
print(values) # [0, 2, 4, 6, 8, 10]
print(index) # 10
}
{
# For loops support break and continue
var values: List[Integer] = []
for i in 0...10: {
if i % 2: {
continue
elif i == 6:
break
}
values.push(i)
}
print(values) # [0, 2, 4]
}
{
var values: List[Integer] = []
# Modifications to the index and end are overwritten.
var start = 0
var end = 5
for i in start...end: {
values.push(i)
end = start
i = start
}
print(values) # [0, 1, 2, 3, 4, 5]
}
Version 2.2 introduces the list style: for (i,) elem in list
.
If one variable is given, it is the element. If two are given, the first is the index and the second is the element.
This style allows either using existing variables or creating new ones. If using existing variables, they must be local variables.
{
var elements: List[String] = []
for elem in ["a", "b", "c"]: {
elements.push(elem)
}
print(elements) # ["a", "b", "c"]
}
{
var indexes: List[Integer] = []
for i, elem in ["a", "b", "c"]: {
indexes.push(i)
}
print(indexes) # [0, 1, 2]
}
{
define range_fn {
# Existing variables must be locals
var i = 0
var elem = ""
for i, elem in ["a", "b", "c"]: {}
print(i) # 3
print(elem) # c
}
range_fn()
}
Exhaustive selection on an enum or class.
{
var v = Some("body")
var message = ""
match v: {
# Decomposition creates 's' in this scope.
# All fields in the variant must be decomposed.
case Some(s):
# Decomposition creates 's' in this scope.
message = s
case None:
}
print(message) # body
#[
# Syntax error, because decomposition must name all vars.
match v: {
case Some(s):
}
]#
}
enum Color {
Red,
Blue,
Green,
RGB(Integer, Integer, Integer)
}
{
var color = RGB(0xFF, 0xCC, 0xDD)
# Use _ to skip decomposition.
match color: {
case RGB(_, blue, _):
var f = "Custom color (blue: {}).".format(blue)
print(f) # Custom color (blue: 204).
case Red:
case Blue:
case Green:
}
# Empty variants can be multi-matched.
match color: {
case RGB(_, blue, _):
case Red, Blue, Green:
}
}
scoped enum ScopedColor {
Red,
Blue,
Green,
RGB(Integer, Integer, Integer)
}
{
# When matching against a scoped enum, the scope must be provided.
var color = ScopedColor.RGB(0xff, 0xcc, 0xdd)
match color: {
case ScopedColor.RGB(_, _, _):
case ScopedColor.Red,
ScopedColor.Blue,
ScopedColor.Green:
}
}
class MyValueError(message: String, code: Integer) < ValueError(message)
{
public var @code = code
}
{
var v: Exception = MyValueError("asdf", 1234)
var code = 0
# Match against a class always decomposes to one var.
# Unlike try, match only works against the exact class.
match v: {
case ValueError(e):
code = 1
case MyValueError(e):
code = e.code
# An else case is required, but can be empty.
else:
}
print(code) # 1234
}
class GenericError[A](message: String) < Exception(message)
{}
{
#[
# Cannot match against a generic class.
var v: Exception = GenericError("")
match v: {
case GenericError(e):
else:
}
]#
}
{
var caught = false
try: {
1 / 0
except IndexError:
# This branch is ignored.
except DivisionByZeroError:
caught = true
}
print(caught) # true
}
{
# A parent class of the raised exception can also be given.
# Use "as" to capture the exception and use it.
var str = "a1"
var error_message = ""
try: {
str.parse_i().unwrap()
except Exception as e:
error_message = e.message
}
print(error_message) # unwrap called on None.
}
class MyValueError(message: String) < ValueError(message)
{ }
{
# Errors can be raised through raise.
# Subclass matching is allowed.
var message = ""
try: {
raise MyValueError("Hello")
except ValueError as e:
message = e.message
except DivisionByZeroError as e:
0 / 0
}
print(message) # Hello
}
# Custom errors are also possible.
class MyError(message: String, code: Integer) < Exception(message)
{
public var @code = code
}
{
var code = 1
try: {
raise MyError("Oh no", 100)
except MyError as e:
code = e.code
}
print(code) # 100
}
var values: List[Integer] = []
var i = 0
while i != 5: {
values.push(i)
i += 1
}
print(values) # [0, 1, 2, 3, 4]
values = []
i = 0
# While loops support break and continue.
while i != 5: {
if i % 2: {
i += 1
continue
elif i == 4:
break
else:
values.push(i)
i += 1
}
}
print(values) # [0, 2]
Match against a specific case.
var v = Some(1)
# Using match to extract a specific case.
match v: {
case Some(s):
print(s) # 1
case None:
}
# Using "with" instead.
with v as Some(s): {
print(s) # 1
}
# With allows else.
with v as None: {
else:
print(v) # Some(1)
}
When used against a class, an exact class instance must be used.
var error: Exception = IndexError("")
with error as Exception(e): {
# This will not be executed.
0 / 0
}