-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtour.bbl
414 lines (350 loc) · 15.4 KB
/
tour.bbl
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
// This is a "Learn X in Y minutes"-style tour of the Babble programming language. It assumes that
// you've used other programming languages before, but not in particular with any of the languages
// that Babble has taken inspiration from.
//
// This file is best read in VS Code with Babble's syntax highlighting enabled (see the README).
//
// Babble's comments are denoted by //, like this one!
//
// Lines of Babble code end with a `.`, though this is allowed to be omitted at the end of a block.
// -------------------------------------------------------------------------------------------------
// 1. Primitive Types
// -------------------------------------------------------------------------------------------------
// Integers:
3. // => 3
// There are binary operators for core maths:
3 + 4. // => 7
5 - 2. // => 3
3 * 4. // => 12
6 / 2. // => 3
// Division always produces another integer:
7 / 2. // => 3
// To keep Babble's precedence rules simple, chains of binary expressions are always evaluated
// left-to-right. Parentheses can be used to group expressions to override this:
2 + 3 * 4. // => 20
2 + (3 * 4). // => 14
// Strings - use curly braces to interpolate values into strings:
"Hello, world!". // => "Hello, world!"
"2 + 2 is {2 + 2}.". // => "2 + 2 is 4."
// Booleans:
true.
false.
// Null, which represents the absence of a value:
null.
// -------------------------------------------------------------------------------------------------
// 2. Method Calls
// -------------------------------------------------------------------------------------------------
// Calling methods looks different to other languages you might be used to.
//
// Methods with no arguments, *unary* methods, are called by writing the method name after the
// expression you'd like to call the method on (the *receiver*):
3 negate. // => -3
"hello" length. // => 5
// Methods which need arguments, *parameterised* methods, are called using the name of each argument
// followed by a colon, then the argument value.
8 modulo: 3. // => 2
"hello" replace: "l" with: "w". // => "hewwo"
// We talk about method names in terms of the parameters the method take - the above examples are
// calling method we would name `negate`, `length`, `modulo:` and `replace:with:`.
// Those binary operators from before are just a shortcut for parameterised method calls:
3 + 2. // is equivalent to...
3 add: 2. // => 5
// Call and operator precedence has a straightforward definition: unary calls bind tightest,
// followed by binary calls, then parameterised calls:
5 mul: 3 negate + 2 negate. // is equivalent to...
5 mul: ((3 negate) add: (2 negate)). // => -25
// Chains of parameterised method calls can start looking a bit messy since you'll need to wrap them
// in parentheses, so Babble provides the `$` operator which wraps everything to its left in
// parentheses:
(("hello" replace: "l" with: "ll") replace: "e" with: "ee") replace: "h" with: "y". // => is equivalent to...
"hello" replace: "l" with: "ll" $ replace: "e" with: "ee" $ replace: "h" with: "y". // "yeellllo"
// -------------------------------------------------------------------------------------------------
// 3. Variables and Collections
// -------------------------------------------------------------------------------------------------
// Local variables are created when they are first assigned to:
x = 3.
y = 4.
z = 5.
// They can be mutated freely:
x = 6.
// Every object has an `equals:` method:
x equals: 6. // => true
x equals: 7. // => false
// Print out variables to the console:
Console println: x. // => prints "6"
// Arrays can be created using the `#{}` syntax:
a = #{ "first" "second" "third" }.
// Array elements in this syntax can be separated using `.` if necessary:
b = #{ x. y. z }. // would be interpreted as a sequence of unary method calls `#{ ((x y) z) }`
// without the separators
// Get and set array elements with `get:` and `set:value:` - indexes are 0-based:
a get: 1. // => "second"
a set: 0 value: "1st".
a. // => #{ "1st" "second" "third" }
// Arrays can change size dynamically:
a append: "end".
a delete: 0.
a insert: "start" at: 0.
a. // => #{ "start" "second" "third" "end" }
// -------------------------------------------------------------------------------------------------
// 4. Blocks
// -------------------------------------------------------------------------------------------------
// Blocks are snippets of code which you can call with parameters. They're like functions, except
// they don't necessarily need to have a name, and you can pass them around like any other value.
//
// Blocks return the last value inside them.
//
// They're similar to `lambda` in Python, or JavaScript's `=>` syntax.
// A block with no parameters - call it with `call`
message = [
"hello"
].
message call. // => "hello"
// A block with one parameter, inside pipes `|` - call it with `call:`
increment = [ |x|
x + 1
].
increment call: 3. // => 4
// A block with multiple parameters - call it with `callWith:` and pass an array of parameters
add = [ |x y|
x + y
].
add callWith: #{ 3 4 }. // => 7
// Blocks capture local variables when they are created, and can mutate them:
x = 3.
mutatingIncrement = [
x = x + 1
].
mutatingIncrement call.
mutatingIncrement call.
x. // => 5
// There is a shorthand for blocks which take a single parameter and call a unary method on it,
// which is useful for list manipulation, indicated with `&`:
a = #{ "hey" "hi" "hello" }.
a map: [ |x| x length ]. // => #{ 3 2 5 }, or equivalently...
a map: &length. // => #{ 3 2 5 }
// -------------------------------------------------------------------------------------------------
// 5. Control Flow
// -------------------------------------------------------------------------------------------------
// There are many methods which take blocks and execute them only conditionally, or multiple times.
// This is how Babble's control flow is implemented!
// Booleans have an `ifTrue:` method, which takes a block and executes it if the receiver is `true`:
d6Roll = Random sample: #{ 1 2 3 4 5 6 }.
d6Roll equals: 6 $ ifTrue: [
Console println: "Wow, you rolled a 6!"
].
// `ifTrue:else:` executes the first block on `true`, and the second on `false`, returning the
// result of whichever executed:
d4Roll = Random sample: #{ 1 2 3 4 }.
d4Result = d4Roll equals: 4 $ ifTrue: [
"Nice roll!"
] else: [
"Better luck next time..."
].
Console println: d4Result.
// Count-based looping can be accomplished with integers' `times:` method, which takes a block and
// executes it that many times, passing a counter:
5 times: [ |i|
Console println: i.
]. // => prints 0, 1, 2, 3, 4
// Loop over every element in an array with `forEach:`:
#{ "first" "second" "third" } forEach: [ |e|
Console println: e.
]. // => prints "first", "second", "third"
// Blocks have a `whileTrue:` method which executes a given block repeatedly, while the receiver
// block returns `true`:
i = 0.
[ i lessThan: 5 ] whileTrue: [
Console println: i.
i = i + 1.
]. // => prints 0, 1, 2, 3, 4
// -------------------------------------------------------------------------------------------------
// 6. Data Types
// -------------------------------------------------------------------------------------------------
// There are two ways of defining data structures in Babble - structs and enums. (If you have used
// Rust, these line up exactly with the same names there!)
// Structs have a name and a list of fields. They automatically have field accessors named after
// their fields, and a constructor whose name is the sequence of the fields in order, and the fields
// are mutable:
struct Person name age.
me = Person name: "Aaron" age: 22. // Constructor is `name:age:`.
me = Person age: 22 name: "Aaron". // Constructors are `unordered func`s, so you can call them with
// parameters in any order!
me name. // => "Aaron"
me age = me age + 1. // (Happy birthday!)
// Structs can have static fields, which exist on the struct itself rather than its instances:
struct Counter static count.
Counter count = 0.
// Enums have a number of variants, and each instance of the enum is an instance of one of those
// specific variants. Enums can optionally have fields per-variant. Instantiating a variant uses
// special syntax with a #
enum Shape {
Rectangle width height.
Circle radius.
Square length.
}
rect = Shape#Rectangle width: 300 height: 100.
rect width. // => 300
// Once you have a data type, methods can be added to it using an `impl` block:
impl Person {
// Unary method definition
func haveBirthday {
// Use `self` to get the receiver
self age = self age + 1
}
// Parameterised method definition, which in this case is also static
// (Exists on the type rather than its instances)
//
// .---- External name (what the parameter is named when calling this method)
// | .---- Internal name (what local variable this parameter's value is bound to)
// v v
static func newborn: name {
// The receiver of a static method is the type itself, so this calls the constructor
self name: name age: 0.
}
}
p = Person newborn: "Baby".
p haveBirthday.
p age. // => 1
// You can use `impl` blocks on any type, any number of times, so you can even extend existing types
impl Integer {
static func meaningOfLife {
42
}
}
// If `self` is an enum (or an instance of an enum), you can omit the name of the enum when
// constructing variants:
impl Shape {
static func unitSquare {
// No need for `Shape#Square`
#Square length: 1.
}
}
// If you'd like to define some methods, but they don't make sense to be part of an instantiable
// type (e.g. some utility methods), you can define a struct with no fields which can't be
// instantiated, and `impl` static methods on it:
struct Utilities.
impl Utilities {
// ...
}
// -------------------------------------------------------------------------------------------------
// 7. Pattern Matching
// -------------------------------------------------------------------------------------------------
// Blocks don't just have to take simple, named parameters!
//
// You can use pattern matching to destructure values and bind them to local variables, and abort
// calling the block if the pattern doesn't match.
//
// One form of these blocks can be created by prefixing the block with `?`, and using the pattern
// instead of the normal parameter list:
addInts = ?[
// Ensure that both parameters are `Integer` instances, and bind them to `a` and `b`
| a = Integer. b = Integer |
a + b
].
rectArea = ?[
// Ensure that the parameter is a `Rectangle` variant of `Shape`, and extract the width and
// height into `w` and `h` respectively, ensuring that they are integers
| Shape#Rectangle width: (w = Integer) height: (h = Integer) |
w * h
].
// `?[ ... ]` blocks are called like normal blocks, but will instead return a variant of the `Match`
// enum, with either:
// - `Match#Hit value` if the pattern matched, with the return value of the block
// - `Match#Miss` if the pattern did not match and the block didn't run
addInts callWith: #{ 1 2 }. // => Match#Hit value: 3
addInts callWith: #{ 1 "two" }. // => Match#Miss
rectArea call: (Shape#Rectangle width: 10 height: 20). // => Match#Hit value: 200
rectArea call: (Shape#Circle radius: 10). // => Match#Miss
// The `Match` type provides static methods to allow you build up a chain of pattern matches, for
// example `value:mustBeOneOf:`:
impl Shape {
func area {
Match value: self mustBeOneOf: #{
?[ | Shape#Rectangle width: (w = Integer) height: (h = Integer) | w * h ]
?[ | Shape#Circle radius: (r = Integer) | r * r * 3 ]
?[ | Shape#Square length: (l = Integer) | l * l ]
}
}
}
(Shape#Rectangle width: 10 height: 20) area. // => 200
(Shape#Square length: 10) area. // => 100
// As a convenience, many of the static methods on `Match` are available through a `MatchProxy`,
// obtained by calling `match` on any value:
(Shape#Rectangle width: 10 height: 20) match mustBeOneOf: #{
// ...same as Match value:mustBeOneOf:
?[ |Shape#Rectangle width: (w = Integer) height: (h = Integer) | ]
}.
// `![ ... ]` blocks behave almost the same, but will return the value not wrapped in a `Match`, and
// fatally error if the pattern didn't match:
addIntsOrError = ![
| a = Integer. b = Integer |
a + b
].
addIntsOrError callWith: #{ 1 2 }. // => 3
// addIntsOrError callWith: #{ 1 "two" }. // => Would cause a fatal error!
// -------------------------------------------------------------------------------------------------
// 8. Mixins
// -------------------------------------------------------------------------------------------------
// Mixins are reusable modules which can be included in structs, enums, and other mixins. After
// creating a mixin, you can `impl` on it like any other type:
mixin CanGreet.
impl CanGreet {
func greet {
Console print: "Hello, my name is ".
// We are assuming that the receiver has a `name` method - it's the responsibility of the
// type which `use`s this mixin to make sure it's meeting our requirements!
Console println: self name.
}
}
// To include this mixin's methods, you can `use` it in another `impl` block:
impl Person {
use CanGreet.
}
me greet. // => Prints "Hello, my name is Aaron"
// Mixins can also be added to a type itself with `static use`, which treats the mixin's instance
// methods as static methods on the type (and discards the mixin's static methods):
struct StaticPerson static name.
StaticPerson name = "Joe Bloggs".
impl StaticPerson {
static use CanGreet.
}
StaticPerson greet. // => Prints "Hello, my name is Joe Bloggs"
// -------------------------------------------------------------------------------------------------
// 9. Advanced Control Flow
// -------------------------------------------------------------------------------------------------
// The `return` keyword will return from the current function (not block), optionally with a given
// value:
impl Array {
func firstEvenNumber {
self forEach: [ |i|
i modulo: 2 $ equals: 0 $ ifTrue: [
// We found the first even number, so we can exit this function and return it!
return i
]
].
null
}
}
#{ 1 3 4 5 10 } firstEvenNumber. // => 4
// If you need even more flexibility, the "throw/catch" system allows you to jump up the call stack
// (throw), and stop at a given point (catch).
//
// The best way of doing this is `Program`'s static method `catchTag:`, which creates a new unique
// tag and allows you to exit the block early by calling `throw:` with it. Here's another
// implementation of `firstEvenNumber`:
impl Array {
func firstEvenNumberAgain {
result = null.
Program catchTag: [ |tag|
self forEach: [ |i|
i modulo: 2 $ equals: 0 $ ifTrue: [
Program throw: tag // Jumps to... -.
] // |
]. // |
]. // ...the end of the block <-----------------------------'
result
}
}
#{ 1 3 4 5 10 } firstEvenNumberAgain. // => 4
// (In fact, `return` is just syntactic sugar for throw and catch!)