about summary refs log blame commit diff stats
path: root/lil/learn.lil
blob: 2e447899a356b80459e5f01fe3f7e2d8f41e80a1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426









































































































































































































































































































































































































































                                                                                          
# 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"