# an octothorpe begins a line comment.
# all whitespace is equivalent; indentation is stylistic.
###################################################
#
# 1. Simple Datatypes and Variables
#
###################################################
"one" # strings are enclosed in double-quotes,
"two\nthree\"four" # and may include the escapes \n \" \\.
-23.84 # numbers are floating point values.
2*3+5 # -> 16 # expressions evaluate right-to-left,
(2*3)+5 # -> 11 # ...unless overridden by parentheses.
() # -> () # the empty list
11,22 # -> (11,22) # "," joins values to form lists.
list 42 # -> (42) # "list" makes a list of count 1.
a:42 # -> 42 # the symbol ":" ("becomes") performs assignment.
z # -> 0 # referencing unbound variables returns 0.
d:("a","b") dict 11,22 # "dict" makes a dictionary from lists of keys and values.
keys d # -> ("a","b") # "keys" extracts the keys of a dict.
range d # -> (11,22) # "range" extracts the elements of a dict.
range 4 # -> (0,1,2,3) # ...or generates a sequence of integers [0,n).
(3,5,7)[1] # -> 5 # lists can be indexed with [], and count from 0.
d.a # -> 11 # dicts can be indexed with a dot and a name,
d["b"] # -> 22 # ...or equivalently, with []; required for non-string keys.
# collection operators:
count 11,22,33 # -> 3
first 11,22,33 # -> 11
last 11,22,33 # -> 33
2 take 11,22,33 # -> (11,22)
2 drop 11,22,33 # -> (33)
typeof 11,22,33 # -> "list"
###################################################
#
# 2. Control Flow
#
###################################################
# the "each" loop iterates over the elements of a list, string, or dict:
each v k i in ("a","b","c") dict 11,22,33
# up to three variable names: value, key, and index.
show[2*v k i] # show[] prettyprints values to stdout.
end
# prints:
# 22 "a" 0
# 44 "b" 1
# 66 "c" 2
# ...and returns the dictionary {"a":22,"b":44,"c":66}.
# the "while" loop:
v:2
while v<256
show[v:v*4]
end
# prints:
# 8
# 32
# 128
# 512
# ...and returns the number 512.
# conditionals:
if age<21
"can't rent cars"
elseif age>65
"senior discounts"
else
"just a regular slob"
end
# "if" and "while" treat 0, or the empty string, list, or dict as "falsey".
# any other value is considered "truthy".
if 3 "yes" end # -> "yes"
if () "no" end # -> 0
###################################################
#
# 3. Functions and Scope
#
###################################################
on myfunc x y do # functions are declared with "on".
show[x y] # function body can contain any number of expressions.
x + 10 * y # function body returns the last expression.
end
# call functions with []
# parameters are NOT separated by commas!
myfunc[2 5] # -> 52
# functions are values, and can be passed around:
on twice f x do
f[f[x]]
end
twice[on double x do x*2 end 10] # -> 40
# functions can be given a variadic argument with "..."
on vary ...x do
show[x]
end
f[11 22 33] # -> (11,22,33)
# variables have lexical scope:
on outer x do # function argument defines local x.
on inner x do # function argument shadows outer x.
x:x*2 # redefine the local x, don't mutate the outer x.
show[x] # -> 10
local z:99 # "local" explicitly declares a local variable.
show[z] # -> 99
end
inner[x]
show[x] # -> 5
show[z] # -> 0 # z is not defined in this scope.
end
outer[5]
###################################################
#
# 4. Tables and Queries
#
###################################################
# make tables with "insert":
people:insert name age job with
"Alice" 25 "Development"
"Sam" 28 "Sales"
"Thomas" 40 "Development"
"Sara" 34 "Development"
"Walter" 43 "Accounting"
end
# tables are like string-keyed dictionaries of uniform-count columns.
# tables are like a list of dictionaries with the same string keys.
people.name[2] # -> "Thomas"
people[2].name # -> "Thomas"
# query tables using a SQL-like syntax:
select from people
# +----------+-----+---------------+
# | name | age | job |
# +----------+-----+---------------+
# | "Alice" | 25 | "Development" |
# | "Sam" | 28 | "Sales" |
# | "Thomas" | 40 | "Development" |
# | "Sara" | 34 | "Development" |
# | "Walter" | 43 | "Accounting" |
# +----------+-----+---------------+
# naming and computing columns, filtering with "where":
select firstName:name dogYears:7*age where job="Development" from people
# +-----------+----------+
# | firstName | dogYears |
# +-----------+----------+
# | "Alice" | 175 |
# | "Thomas" | 280 |
# | "Sara" | 238 |
# +-----------+----------+
# summarizing with "by" (groupby):
select job:first job employees:count name by job from people
# +---------------+-----------+
# | job | employees |
# +---------------+-----------+
# | "Development" | 3 |
# | "Sales" | 1 |
# | "Accounting" | 1 |
# +---------------+-----------+
# "update" works like "select" but patches selected cells of the table.
# note that tables, like lists and dicts, are immutable!
# "update" returns a new, amended table value and does not reassign the variable "people":
update job:"Software Engineering" where job="Development" from people
# +----------+-----+------------------------+
# | name | age | job |
# +----------+-----+------------------------+
# | "Alice" | 25 | "Software Engineering" |
# | "Sam" | 28 | "Sales" |
# | "Thomas" | 40 | "Software Engineering" |
# | "Sara" | 34 | "Software Engineering" |
# | "Walter" | 43 | "Accounting" |
# +----------+-----+------------------------+
# queries apply to strings, lists, dicts, with columns "key", "value", "index".
# sorting with "orderby" (asc for ascending, desc for descending):
select index value orderby value asc from "BACB"
# +-------+-------+
# | index | value |
# +-------+-------+
# | 1 | "A" |
# | 0 | "B" |
# | 3 | "B" |
# | 2 | "C" |
# +-------+-------+
# "extract" works like "select", but returns a list instead of a table:
extract name orderby name asc from people
# -> ("Alice","Sam","Sara","Thomas","Walter")
# if you don't specify a column expression, extracts the first column:
extract orderby name asc from people
# (same as above)
# more table and collection operators:
a join b # natural join (by column name).
a cross b # cross join / cartesian product.
a , b # concatenate rows of two tables.
3 limit t # at most 3 rows of a table.
rows t # list of rows as dictionaries.
cols t # dictionary of columns as lists.
table x # make a table from dict-of-list / list-of-dict.
raze t # form a dictionary from the first two columns of a table.
flip x # transpose a table's rows and columns.
###################################################
#
# 5. String Manipulation
#
###################################################
",|" split "one,|two,|three" # break a string on a delimiter.
# -> ("one","two","three")
"::" fuse ("one","two","three") # concatenate strings with a delimiter.
# -> "one::two::three"
# the "format" operator uses a printf()-like format string
# to control converting one or more values into a string:
"%i : %s : %f" format (42,"Apple",-23.7)
# -> "42 : Apple : -23.7"
"%08.4f" format pi # 0-padding, field width, precision.
# -> "003.1416"
"%l : %u" format "One","Two" # lowercase or uppercase.
# -> "one : TWO"
d:("a","b") dict 11,22
"%j" format list d # JSON.
# -> "{\"a\":11,\"b\":22}"
# the "parse" operator tokenizes a string into values.
# "parse" and "format" use the same pattern syntax:
"%v[%i]" parse "foo[23]"
# -> ("foo",23)
"%j" parse "{'foo':[1,2,],'bar':34.5}" # tolerant JSON parsing.
# -> {"foo":(1,2),"bar":34.5}
"< %[b]s %[a]s >" format d # named elements.
# -> "< 22 11 >"
# the "like" operator performs glob-matching:
"foo" like "f*" # -> 1 (prefix match; * matches 0 or more chars)
"foo" like "*oo" # -> 1 (suffix match)
"fxo" like "f.o" # -> 1 (. matches any single char)
"a4" like "a#" # -> 1 (# matches any single digit)
"c#" like ".`#" # -> 1 (` escapes special chars)
("a2","b2") like "a#" # -> (1,0) (left argument can be a column)
###################################################
#
# 6. Implicit Iteration
#
###################################################
# arithmetic operators "conform" over lists;
# this is how column expressions in queries work, too:
1+2 # -> 3 (number plus number)
1+10,20,30 # -> (11,21,31) (number plus each of list)
(10,20,30)+1 # -> (11,22,31) (each of list plus number)
(10,100,1000)+1,2,3 # -> (11,102,1003) (each of list plus each of list)
# the "=" equality operator conforms; use in query expressions:
3=3 # 1
3=2,3,5 # (0,1,0)
# the "~" equality operator compares entire values; use with "if":
3~3 # -> 1
3~2,3,5 # -> 0
# the "@" operator applies the left argument to each item on the right:
(11,22,33) @ 2,1,2 # -> (33,22,33) (indexing a list)
double @ 10,20 # -> (20,40) (applying a function)
first @ ("Alpha","Beta") # -> ("A","B") (applying a unary operator)
# ".." is shorthand for "at every index":
t:"%j" parse "[{'a':22,'b':33},{'a':55}]"
t..a # -> 22,55
each v in t v.a end # -> 22,55
# unary operators for reducing lists to a residue:
sum 1,2,3 # -> 6
prod 2,3,4 # -> 24 (product)
min 9,5,7 # -> 5 (minimum; all)
max 9,5,7 # -> 9 (maximum; some)
raze ((list 1,2),(list 3,4)) # -> 1,2,3,4 (flatten nesting)
###################################################
#
# 7. Arithmetic and Logic
#
###################################################
2 ^ 3 # -> 8 (exponentiation)
5 % 3,4,5,6,7 # -> (3,4,0,1,2) (y modulo x)
3 | 5 # -> 5 (maximum; logical OR for 0/1)
3 & 5 # -> 3 (minimum; logical AND for 0/1)
3 < 2,3,5 # -> (0,0,1) (less)
3 > 2,3,5 # -> (1,0,0) (more)
! 0,1,3,5 # -> (1,0,0,0) (not)
# no x<=y or x>=y operators; use !x>y or !x<y instead
# named unary operators, which all conform:
# floor cos sin tan exp ln sqrt
# the constants "pi" and "e" are predefined as globals
mag 3,4 # -> 5 (vector magnitude)
u:unit pi/3 # -> (0.5,0.866025) (unit vector at angle)
(pi/3)~heading u # -> 1 (angle of vector)
###################################################
#
# 8. Interfaces and Built-in Functions
#
###################################################
# interfaces are dictionary-like values representing system resources, mutable data.
# singleton utility interfaces:
sys.now # sys: workspace info, rng, time
bits.xor[3 42] # bits: bit-wise arithmetic
rtext.replace["the orange!" "orange" "apple"] # rtext: rich text tables
# built-in functions for constructing instances of certain interfaces:
arr:array[16 "i16b"] # array: a 1D mutable array for manipulating binary data
img:image[(10,20)] # image: a 2D mutable byte array for graphics operations
sys~sys # -> 1 # interfaces compare with reference-equality.
sys=(sys,sys,123) # -> (1,1,0)
typeof sys # -> "system" # two ways to identify interface type.
sys.type # -> "system"
keys sys # -> () # interfaces are non-enumerable!
# more built-in functions:
eval[str vars] # evaluate a string of Lil with a dict of local variable bindings
random[x] # choose a random element from a string, list, dict, or table
readcsv[x] # parse a string with CSV data into a table
writecsv[x] # format a table into a CSV string
readxml[x] # parse an XML/HTML document into a tree of dictionaries
writexml[x] # format a tree of dictionaries into well-formed XML
###################################################
#
# 9. Decker
#
###################################################
# Decker is a GUI-builder like HyperCard or VisualBASIC:
go["http://beyondloom.com/decker/"]
# the parts of a "Deck" have corresponding interfaces, and can be given scripts.
# if a Widget, Card, or Deck contains function definitions with specific names,
# they will be called in response to events:
on click do # (imagine this is in a Button's script)
me # the current Widget's interface.
card # the Card interface containing the Widget.
deck # the Deck interface containing the Card.
myField.text:1+myField.text # we can refer to other widgets on the same card by name.
# we can also refer to Cards in the Deck by name.
# for a Widget on another Card, index through "card.widgets":
f:otherCard.widgets.otherField
f.text:f.text+1
# we can send "synthetic" events to other parts of the Deck:
otherButton.event["click"]
end
# each event handler is its own fresh universe; variables do not persist.
# the only state preserved between events is state stored in Widgets!
field.text # string.
button.value # 1/0 boolean.
slider.value # number.
grid.value # table.
canvas.copy[] # Image interface.
# ...and so on. Contraptions are custom widgets, with a user-defined API.
# we can also treat Cards like records, with consistently-named Widgets as their fields.
# consider a checkbox on each Card in a Deck tracking whether it had been visited:
on view do # (in the script for each Card)
visited.value:1 # "visited" is a checkbox.
end
# we could then find the Cards that have been visited so far with a query:
extract value where value..widgets.visited.value from deck.cards
# ...or reset all our visited flags:
deck.cards..widgets.visited.value:0
# we can override builtins and insert side-effects:
on go x trans delay do
if trans show["start transition: " trans] end
# "send" here calls the definition of "go" in the next-highest lexical scope:
send go[x trans delay]
end
# if unhandled, events "bubble up": Widget -> Card -> Deck.
# to prevent bubbling, define an empty handler:
on navigate x do end
# some important attributes that apply to all Widgets:
x.pos # (x,y) position on the Card, in pixels.
x.size # (width,height) dimensions, in pixels.
x.name # string uniquely identifying this Widget on a Card.
x.index # drawing order on the Card, back-to-front.
x.show # one of "solid", "invert", "transparent", or "none"