-
Notifications
You must be signed in to change notification settings - Fork 3
/
learn-wry.wry.txt
718 lines (562 loc) · 20.7 KB
/
learn-wry.wry.txt
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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
project : Learn Wry
author : Mike Weaver
created : 2018-08-29
copyright : © 2018 All rights reserved.
license : See [*LICENSE].
section : Hello Wry
Following long-standing tradition, here is a Wry script that prints a message
to the screen. The code itself is only one line, but a Wry file includes not
only code, but also prose for documentation (although the prose in the
example below is, admittedly, very minimal and not a good example of
documentation):
``` : K&R homage
project : My first Wry script
<<<
print('hello, world')
>>>
A typical Wry document begins in prose and code is written between `<<<` and
`>>>` markers. For the remainder of this document we will show Wry code on
its own without the surrounding prose or markers. To learn more about Wry
prose, see {Introduction to Wry}[*intro.wry]. Or just study the structure of
this document, which itself is valid Wry prose.
section : Simple types
Wry has 5 familiar scalar types, listed in the {table}[*Wry types] below with
examples of corresponding literals.
+++ : Wry types
Type :: Examples
int :: 42, 0, -17
float :: 4.5, -18.4, 1e-6
bool :: true, false
string :: 'hello, world'
null :: null
section : Storing and retrieving values
To store values in the current scope, use the assignment operator `=`. On the
left side provide a key in the form of a variable or a key expression. A "key
expression" is an expression enclosed in brackets that evaluates to a string
or integer. A variable is a name which must start with a letter or underscore
and must not contain special characters. You don't put quotes around it, but
a variable is treated as a string key.
!!!
<variable> = <value> is the same as ['<variable>'] = <value>
and <value> is the same as [] = <value>
``` : Setting values
name = 'Fred' // Store 'Fred' using key 'name'
['age'] = 42 // Store 42 using key 'age'
hasCookies = false // Store false using key 'hasCookies'
[0] = 'zero' // Store 'zero' using key 0
15 // Store 15 using key 1, same as `[] = 15`
A statement that is a bare expression, such as the final line in the example
above, will be stored in the current scope with the next available integer
key (in this case 1, since 0 was previously used).
To refer to a value stored in the current scope use a variable or a key
expression.
``` : Retrieving values
print('Hi \{['name']},') // this looks klunky, can it be `\['name']`?
print('You are \{age} years old.')
print('You have \{0} cookies.') // --A--
print('You have \{5-5} cookies.') // --B--
Some things to note:
1.
We switched between a variable and a key expression when setting and
retrieving `name` and `age`. We have the option to use either syntax
because those string keys are valid names. With integer keys, however, or
with string keys that are not valid names, we must use a key expression.
2.
Line B shows an example of a key expression that uses a more "complex"
expression inside the brackets. It produces the same result as line A.
3.
String interpolation is accomplished by wrapping expressions in \{}.
!!!
I'm thinking that if the expression is simple enough, the {} should be
optional. So `\{['name']}` could instead be `\['name']`, and \{age} could
simply be \age. You would only need the curly brackets if the expression
was confused with the surrounding text.
section : Arrays
In addition to the scalar types, Wry has a compound type: `array`. An array
is an ordered map, which means it preserves the order of its key-value pairs
as they are added. Keys must be of type `string` or `int`.
The most general way to create an array is with an array block. Write a
variable or a key expression followed by indented assignment statements. As
mentioned above, bare expressions generate automatic integer keys.
``` : Array block creation
person
first = 'Joe'
last = 'Smith'
age = 42
['names']
'Tom' // These bare expressions
'Harry' // will automatically be
'Fred' // assigned keys 0, 1, 2.
cutoffs
[1] = 10.5
[2] = 21.5
[3] = 45.0
In the statements above, each about-to-be-created array is considered the
"current scope" for the the indented assignment statements that follow.
Non-assignment statements can be used in the array block as well, such as
branching or looping statements which are described in the [*Control flow]
section below.
Alternatively, an array can be created "in-line" with a comma-separated list
of assignments. The following lines produce the same results as above. With
inline array assignment, parenthesis are needed because `=` has higher
precedence than `,`.
``` : Creating an array
person = ( first = 'Joe', last = 'Smith', age = 42 )
['friends'] = ( 'Tom', 'Harry', 'Fred' )
cookies = ( [1] = 10, [2] = 21, [3] = 45, ) // Trailing commas OK.
To retrieve values from an array, use `.` membership operator with variables,
or, more generally, use a key expression as a subscript operator.
I want a way to mix in-line expressions with block expressions. When
expressions get really long we want a good way to break them up onto multiple
lines. So the idea is that within parenthesis we can break to multiple lines
and the indent level is reset until the closing parens.
``` : Alternative Array
person = (
first = 'Joe'
last = 'Smith'
age = 42
)
This allows us to use statements, such as `if` or `for` inside the array
expression. Of course in this situation we could drop the equals sign and the
wrapping parenthesis and achieve the same result. But when we are passing
arguments to functions, for example, then the wrapping parens are needed.
``` : Alternative Array with statements
person = (
first = 'Joe'
last = 'Smith'
if old
age = 42
else
age = 24
)
And it allows us to mix these in within expressions.
``` : Array in expression with statements
result = (
'Hello, '
person.first
' You are '
if old
42
else
24
' years old.'
) -> join('')
The general pattern is an opening parens followed by a new line opens a new
context which can contain statements, and the result will be an expression
collapsed back at the point where the parens was opened. If the context
(block) has a single statement, it can be promoted up to the same line as the
open parens:
```
r = a -> b -> c -> d() // If this were a really long expression...
r = (
->
a
b
c
d()
)
r = ( ->
a
b
c
d()
)
if (condition1 and condition2 and condition3)
...
if ( and
condition1
condition2
condition3
)
...
f(key1=val1, key2=val2, key3=val3, ...)
f(
key1=val1
key2=val2
key3=val3
...
)
f<
...
>(
...
)
By this being a context and a statement block, control flow statements can be
interspersed, as well as line comments.
!!!
This presupposes that we have statments such as `->` and `and` that work
as a block statement. Probably also `.` should be a statement, so you can
have a chain of member lookup (like a key path lookup), and '+' for
joining strings.
``` : Array lookup
print('Hi \{person.first},')
print("You are \{person['age']} years old.")
print('One of your friends is \{friends[0]}.')
You can modify an array after creation. The most general approach is to use a
`with` block to "re-open" the array scope for additional assignment
statements (and non-assignment statements).
!!!
I might change this keyword to `use` and reserve `with` for something
else, similar to how Python has `with` blocks that take care of init and
de-init.
``` : `with` block
with person
first = 'Fred'
last = 'Brown'
with ['friends']
[1] = 'Sally'
with cookies
87 // Missing key; will use next integer key.
!!! : `with` with non-array
If one of those (like `person`) existed but wasn't already an array, then
it would be converted to an array with a warning.
The same results can be achieved with `=` assignment operator and `.` or
subscript syntax on the array. The example below uses different combinations
of variables and key expressions.
``` : Modifying an array
person['first'] = 'Fred'
['person'].last = 'Brown'
['names'][1] = 'Sally'
cookies[] = 87 // Missing key; will use next integer key.
To remove a value from an array, set it to `null`. The same technique removes
values from the current scope.
``` : Removing a value from an array
friends[2] = null // So long, Fred.
cookies = null
section : Basic operators
!!!
Do we want a big table of operator precedence? That does need to exist,
eventually, but maybe not here.
Arithmetic operators (% remainder??, overflow?)
Assignment operators (including compound +=,etc. and conditional ?=)
Compound assignment operators (+=, etc.)
Comparison operators
Casting
Bitwise operators (should have higher precedence than comparison)
Ternary condition operator
Range operators
Logical operators
We won't talk about chaining -> or inheritance <- or argument binding <>
or tags #, #[], @, @[], or expansion operator ~ (maybe we call this the
"bind" operator), or ~~ (perhaps the "splat" operator) or keyPath
expansion operator ~[]
Other operators: ?: (leave out middle), chaining those to create an
if-elseif-elseif-else structure ( ? : ? : ? : ); ?? null coalescing; <=>
comparison
What about string interpolation? Allow it with \{}? Speaking of
string interpolation, the latest changes to Swift (5, or maybe 4) have
some really well thought out handling of strings.
section : Control flow
Wry has `if` blocks for branching and `do` and `for` blocks for looping. The
statements that form the body of an `if`, `do`, or `for` block (really, any
block) must be identically indented.
section : Branching
To execute code branches conditionally, use the `if` block, optionally
followed by one or more `else if` blocks, optionally followed by a final
`else` block.
``` : `if` block example
if age < 42
comment = "whipper-snapper"
young = true
else if age < 65
comment = "still working"
else
comment = "codger status"
section : Looping
Wry has two blocks for looping: `do` and `for`. A `do` block executes its
statements at least 1 time; repeat executions are triggered by the
`continue` statement. Within a `do` block, the `continue` statement stops
the current iteration and causes execution to repeat from the top.
``` : `do` block example
age = 0
do
age += 1
...
if age != 42
continue
... // Remainder of `do` block.
In the {example}[*`do` block example] above, the condition that determines
whether or not to repeat is checked and if it is met, the `continue`
statement causes execution to transfer to the top of the loop, skipping
the remainder of the loop.
Suppose we re-structure the code and place the condition at the top, as in
the {example}[*`do` block with top condition check] below.
``` : `do` block with top condition check
age = 0
do
if age != 42
...
age += 1
continue
Keywords `do` and `continue` can be followed with an optional single-line
statement--handy for initializing and incrementing loop indices. In the
prior example the initialization of `age`, rather than preceding the `do`
block, can be written following the `do` keyword and will only be executed
once: `do age = 0`; likewise, the statement to increment `age` can follow
the `continue` keyword: `continue age += 1`. This results in the
following:
``` : `do` and `continue` combined with loop index statements
do age = 0
if age != 42
...
continue age += 1
Often the entire body of a `do` block will comprise, as above, a single
`if` block. To prevent excessive indentation it is common in such cases to
collapse the `do` and `if` to a single line, as shown below.
``` : `do if` example
count = 100
do if count
...
continue count -= 1
!!! : collapsed do-if with initialization
What if we want to initialize on the do statement, and also collapse the if? Does it look like this:
```
do count=100 if count
...
continue count-1
A `for` block loops over elements of an array, providing variables `key`
and `val` to access the current element at each iteration.
``` : `for` block example
attribs
class = 'warn'
title = 'Caution'
content = 'Cookies may contain peanuts'
level = 5
for attribs
print('key is \{key}, value is \{val}')
As with `do` blocks, an inner `if` block that encompasses the entire loop
body can be collapsed to the same line as the `for` keyword. Likewise, a
`continue` statement will stop the current iteration and proceed
immediately with the next. However, outside of this "short-circuit"
situation, the `continue` statement is not needed. Unlike a `do` block
(which without the `continue` statement will only execute one time), the
`for` block will automatically iterate the entire array whether a
`continue` statement is present or not.
section : Transferring control
Use a `break` statement to immediately exit a `do` or `for` loop. An
optional single-line statement can be included with the `break`
keyword.
``` : `break` example
found = false
for names
if val == 'Ted'
break found = true
Both `for` and `do` blocks can be followed with a `then` block, which will
execute after the loop, but only if the loop ran completely and no `break`
statement transferred control out of the loop prematurely. In other words,
`break` transfers control outside the loop _and_ after any trailing
`then` block.
``` : `then` example
found = false
for names
if val == 'Ted'
break found = true
then
... // Executes if no 'break' occurs above.
... // `break` will transfer control here.
``` : Another `then` example
confirmed = false
do
if !cond1
break
if !cond2
break
... // Continue to check confirmation conditions.
then
confirmed = true
// `break` transfers here.
if confirmed
... // Do something knowing we passed all the tests above.
section : Labels
When loops are nested, it can be desirable to `continue` or `break` from
an inner loop to an outer loop. To achieve this, a loop block can include
an optional label on the `do` or `for` keyword, which can then be
referenced from a `continue` or `break` statement. A label, like a
variable, is a name that must start with a letter or underscore and must
not contain special characters. Labels are created with `#` suffix on the
`do` or `for` keyword; referenced with `@` suffix on the `continue` or
`break` keyword.
!!!
Why use different symbols? Why not use # in both places?
``` Loop labels
do#outer i=0 if i<40
do j=0 if j<30
...
if <condition> continue@outer i += 1
... // Remainder of inner j-loop.
... // Remainder of outer i-loop.
In the {example}[*Loop labels] above, the outer loop is labeled with
`#outer` and if the inner condition is met, the `continue@outer i += 1`
statement will increment `i` and then cause control to skip the remainder
of both inner (j) and outer (i) loops and resume with the next iteration
of the outer-most loop.
To distinguish automatic `key` and `val` variables with nested loops, use
angle brackets and provide explicit variable names, in the form
`<key=name>`, on the `for` loop. For example, given a multi-dimensional
array of books grouped by category:
``` : Custom `key` and `val` within nested `for` loops
for category=bookarray in categories
// Outer loop `key` is the category name; `val` is an array of book
// objects.
...
for book in bookArray
// Inner loop `key` is an integer (not used), and `val` is a book
// object.
...
print('book ' + val.name + ' is in category ' + category)
section : Early exit
Use a `guard` block to test for a condition which must be true in order to
continue execution. If the condition fails, the body of the `guard` block
will execute, and it must contain a statement, such as `continue` or
`break`, that transfers control out of, or to the next iteration of, the
current loop.
``` : `guard` block
do
guard <condition-that-must-be-true-to-proceed>
... // Cleanup, error message, etc.
break
... // Proceed assured that above guard condition was met.
... // Failed guard condition transfers control to here.
Note: `guard` is also used to exit (or `return`) early from functions, as
described in the next section.
section : Functions
!!!
Have to decide on syntax here.
```
// option 1
func a
...
// option 2
a = func
...
// inline?
a = { ... }
// and what about binding arguments during declaration?
func<args> a ...
a = func<args> ...
a = { ... }<args>
I think I'm liking the second option. Even though it looks like an
expression, it wouldn't be legal to combine it with other expressions:
```
a = 3 + func
...
would not work. In fact the equals sign is not needed at all
```
myFunc func<bound args>
...
section : Chaining (or maybe "binding")
!!!
Something I've been thinking: the chain/bind operator introduces a new
scope for the purpose of object resolution:
```
a -> b
Means we first look inside `a` to attempt to resolve `b`. If there is a
`b` inside `a`, then that is what will be found and bound.
```
a -> f()
The `execute` operator `()` will have to have lower priority than `->`
because we first have to check for `f` inside of `a`, bind it, and then
execute the resulting function with it's just bound `$obj` scope.
I also want the ability to bind arguments without execution:
```
a -> f<...args...>
section : Scope revisited
!!!
Here can go into more detail on $loc, $obj, $arg, $pre, $cur, $out inside
functions.
```
Suppose object `b` has a function `g` defined, as does object `c`,
intending to override `b`'s function `g` with a modified definition.
Further suppose that function `f` is defined in object `d`.
a -> b -> c -> d -> f()
Inside of `f`, `$obj` will refer to the chain `a->b->c->d`; `$cur` will
point to `d`, where function `f` is defined; `$pre` will point to `c`,
which precedes `d` in the chain. If inside the body of `f` the function
`g` is called, then the same `$obj` will be bound to `g`, but now `$cur`
will point to `c`, where `g` is defined, and `$pre` will point to `b`.
!!!
Wow, that preceding paragraph is complicated. It made sense when I
wrote it, but now it just seems like word soup. The pictures below help
to show what is going on.
when `f` executes:
+------+
| call | <-- $out, f's call site
+------+
| a |
| b |
| c | <-- $pre
| d | <-- $obj, $cur
+------+
| $arg |
+------+
| $loc |
+------+
when `g` is called from `f`:
+-------+
| call | <-- f's call site
+-------+
| f obj | <-- f's object scope (same as g's)
+-------+
| f arg | <-- f's arguments
+-------+
| f loc | <-- $out, g's call site, f's local scope
+-------+
| a |
| b | <-- $pre
| c | <-- $cur
| d | <-- $obj
+-------+
| $arg | <-- g's arguments
+-------+
| $loc | <-- g'w local scope
+-------+
section : Exceptions
!!!
Is this the place to talk about `defer`? Rename the section 'Error
handling' and cover both `defer` and exceptions?
section : Macros
After having studied Lisp and Julia and Clojure for a bit, what if we allowed
for an encoding of an expression, for example the conditional `3<a && 4<b`,
into an array, mirroring the syntax tree:
```
&&
<
3
a
<
4
b
The problem with that, syntactically, is symbols like `&&` and `<` are not
valid as keys. But we could turn them into key expressions.
```
['&&']
['<']
3
a
['<']
4
b
Which is fine. But maybe we have some sugar here to "escape" them.
```
`&&
`<
3
a
`<
4
b
Any string prefixed with a backtick is turned into a string key expression.
```
`word => ["word"]
That could work for both assignment (l-values) and retrieval (r-values) and
allow one to use keywords and operators as array keys.
Back to the tree, this could then be passed to a function, say `eval` that
would turn the keys into a function call, passing the values as arguments.
Literally everything in wry could be coded up in a hierarchical array like
this. This means I have to implement operators and other things as functions
(which of course they will be) but those functions will have to be exposed to
the runtime in such a way that the user could call or override them, instead
of having them locked away accessible only to the interpreter.
This opens up operator overloading, right? If we set a function, `+`,
somewhere along the scope chain, then that function should be called instead
of the one provided by the system.