Rename .markdown files to .md; other small edits in them.
Chris Pressey
5 years ago
0 | Robin | |
1 | ===== | |
2 | ||
3 | _This is a work in progress_ | |
4 | ||
5 | Robin is a homoiconic S-expression-based language (similar to, for example, | |
6 | [Scheme][], with influences from [Pixley][] and [PicoLisp][]) with the | |
7 | following features: | |
8 | ||
9 | * The _macro_ (rather than the function) as the fundamental abstraction | |
10 | mechanism. There is a function form, but it's defined as a macro! | |
11 | * A very small set of built-in operations. | |
12 | * A very small reference implementation in Literate Haskell | |
13 | (about 600 lines of code, excluding the explanatory prose.) | |
14 | * A fairly rich standard library of macros built on top of those built-in | |
15 | operations. (Thus it can be used as either a "low-level" or "high-level" | |
16 | language.) | |
17 | * A fairly rich test suite (about 460 test cases.) | |
18 | * An almost zealous system-agnosticism. | |
19 | * An almost zealous disdain for escape characters. Robin's string syntax | |
20 | never needs them (it's more like a lightweight "heredoc".) | |
21 | * A module system (which is rather fast-and-loose, so it's perhaps not | |
22 | fair to call it a module system. It's more like C's `#include`s — | |
23 | except it's zealously system-agnostic. And actually we're still working | |
24 | out the details here. See the file `doc/Modules.markdown`.) | |
25 | * A(n attempt at) a clean separation of evaluation (no "side-effects") and | |
26 | execution (with "side-effects" and system interaction) by the use of | |
27 | _reactors_ (which are basically event handlers.) See the file | |
28 | `doc/Reactor.markdown` for more information. | |
29 | ||
30 | Quick Start | |
31 | ----------- | |
32 | ||
33 | You'll need either `ghc` or Hugs installed. | |
34 | ||
35 | Clone this repo and `cd` into it, and run `./build.sh` to build the reference | |
36 | interpreter `bin/robinri`, and the slightly-less-impractical interpreter | |
37 | called `bin/whitecap` (for historical reasons, and subject to change.) | |
38 | ||
39 | (Or if you have [toolshelf][], just run `toolshelf dock gh:catseye/robin`.) | |
40 | ||
41 | If you have a few minutes to spare, please do run the tests by running | |
42 | `./test.sh`. (This requires [Falderal][].) | |
43 | ||
44 | (There will be a link to a tutorial with further instructions in the future) | |
45 | ||
46 | Documentation | |
47 | ------------- | |
48 | ||
49 | Robin's fundamental semantics are documented in | |
50 | [doc/Robin.markdown](doc/Robin.markdown). | |
51 | ||
52 | History | |
53 | ------- | |
54 | ||
55 | Robin 0.2 is a somewhat significant departure from Robin 0.1. It keeps: | |
56 | ||
57 | * its syntax | |
58 | * its core builtins (mostly) | |
59 | * some of its standard modules ("small", list, environment, boolean, arith) | |
60 | * exceptions (and makes them standard rather than optional) | |
61 | * its zealous system agnosticism | |
62 | * its zealous disdain for escape characters (i.e. its literal string syntax) | |
63 | ||
64 | Robin 0.2 *discards* from Robin 0.1: | |
65 | ||
66 | * bigrats. Instead, in Robin 0.2 you get 32-bit signed integers (yes, | |
67 | precisely those.) Anything else, you have to build. | |
68 | * its module system. Robin has its own, much less hermetic/holistic | |
69 | system. See the file `doc/Modules.markdown`. | |
70 | * concurrency. | |
71 | * I/O and side-effects. It has reactors instead. See `doc/Reactor.markdown`. | |
72 | * its grand ambitions. Robin would rather exist than be perfect. | |
73 | ||
74 | Robin 0.2 *adds* to Robin 0.1: | |
75 | ||
76 | * _reactors_, which I hope will be a cleaner and more system-agnostic | |
77 | way to do I/O. See `doc/Reactor.markdown`. | |
78 | ||
79 | [Falderal]: http://catseye.tc/node/Falderal | |
80 | [PicoLisp]: http://picolisp.com/ | |
81 | [Pixley]: http://catseye.tc/node/Pixley | |
82 | [Robin]: http://catseye.tc/node/Robin | |
83 | [Scheme]: http://schemers.org/ | |
84 | [toolshelf]: http://catseye.tc/node/toolshelf |
0 | Robin | |
1 | ===== | |
2 | ||
3 | _Version 0.3. Work-in-progress, subject to change._ | |
4 | ||
5 | Robin is a homoiconic S-expression-based language (similar to, for example, | |
6 | [Scheme][], with influences from [Pixley][] and [PicoLisp][]) with the | |
7 | following features: | |
8 | ||
9 | * The _macro_ (rather than the function) as the fundamental abstraction | |
10 | mechanism. There is a function form, but it's defined as a macro! | |
11 | * A very small set of built-in operations. | |
12 | * A very small reference implementation in Literate Haskell | |
13 | (about 600 lines of code, excluding the explanatory prose.) | |
14 | * A fairly rich standard library of macros built on top of those built-in | |
15 | operations. (Thus it can be used as either a "low-level" or "high-level" | |
16 | language.) | |
17 | * A fairly rich test suite (about 460 test cases.) | |
18 | * An almost zealous system-agnosticism. | |
19 | * An almost zealous disdain for escape characters. Robin's string syntax | |
20 | never needs them (it's more like a lightweight "heredoc".) | |
21 | * A module system (which is rather fast-and-loose, so it's perhaps not | |
22 | fair to call it a module system. It's more like C's `#include`s — | |
23 | except it's zealously system-agnostic. And actually we're still working | |
24 | out the details here. See the file [doc/Modules.md](doc/Modules.md).) | |
25 | * A(n attempt at) a clean separation of evaluation (no "side-effects") and | |
26 | execution (with "side-effects" and system interaction) by the use of | |
27 | _reactors_ (which are basically event handlers.) See the file | |
28 | [doc/Reactor.md](doc/Reactor.md) for more information. | |
29 | ||
30 | Quick Start | |
31 | ----------- | |
32 | ||
33 | You'll need either `ghc` or Hugs installed. | |
34 | ||
35 | Clone this repo and `cd` into it, and run `./build.sh` to build the reference | |
36 | interpreter `bin/robinri`, and the slightly-less-impractical interpreter | |
37 | called `bin/whitecap` (for historical reasons, and subject to change.) | |
38 | ||
39 | (Or if you have [shelf][], you can run `shelf_dockgh catseye/robin`.) | |
40 | ||
41 | If you have a few minutes to spare, please do run the tests by running | |
42 | `./test.sh`. (This requires [Falderal][].) | |
43 | ||
44 | (There will be a link to a tutorial with further instructions in the future) | |
45 | ||
46 | Documentation | |
47 | ------------- | |
48 | ||
49 | Robin's fundamental semantics are documented in | |
50 | [doc/Robin.md](doc/Robin.md). | |
51 | ||
52 | History | |
53 | ------- | |
54 | ||
55 | Robin 0.2 is a somewhat significant departure from Robin 0.1. It keeps: | |
56 | ||
57 | * its syntax | |
58 | * its core builtins (mostly) | |
59 | * some of its standard modules ("small", list, environment, boolean, arith) | |
60 | * exceptions (and makes them standard rather than optional) | |
61 | * its zealous system agnosticism | |
62 | * its zealous disdain for escape characters (i.e. its literal string syntax) | |
63 | ||
64 | Robin 0.2 *discards* from Robin 0.1: | |
65 | ||
66 | * bigrats. Instead, in Robin 0.2 you get 32-bit signed integers (yes, | |
67 | precisely those.) Anything else, you have to build. | |
68 | * its module system. Robin has its own, much less hermetic/holistic | |
69 | system. See the file [doc/Modules.md](doc/Modules.md). | |
70 | * concurrency. | |
71 | * I/O and side-effects. It has reactors instead. | |
72 | * its grand ambitions. Robin would rather exist than be perfect. | |
73 | ||
74 | Robin 0.2 *adds* to Robin 0.1: | |
75 | ||
76 | * _reactors_, which I hope will be a cleaner and more system-agnostic | |
77 | way to do I/O. See [doc/Reactor.md](doc/Reactor.md). | |
78 | ||
79 | [Falderal]: https://catseye.tc/node/Falderal | |
80 | [PicoLisp]: http://picolisp.com/ | |
81 | [Pixley]: https://catseye.tc/node/Pixley | |
82 | [Robin]: https://catseye.tc/node/Robin | |
83 | [Scheme]: http://schemers.org/ | |
84 | [shelf]: https://catseye.tc/node/shelf |
0 | Robin Intrinsics | |
1 | ================ | |
2 | ||
3 | -> Tests for functionality "Interpret core Robin Program" | |
4 | ||
5 | An _intrinsic_ is one of the data types in Robin. It is like a macro, except | |
6 | that it is implemented intrinsically (and thus does not support quite | |
7 | every operation that is supported on macros, for example, examining its | |
8 | internals.) | |
9 | ||
10 | Robin provides (as of this writing) 15 intrinsics. These represent | |
11 | the fundamental functionality that is used to evaluate programs, and that | |
12 | cannot be expressed as macros written in Robin (not without resorting to | |
13 | meta-circularity, at any rate.) All other macros are built up on top of | |
14 | the intrinsics. | |
15 | ||
16 | This set of intrinsics is not optional — every Robin implementation must | |
17 | provide them, or it's not Robin. | |
18 | ||
19 | Intrinsics usually have undefined behaviour if their preconditions are | |
20 | not met (i.e., if they are called with wrong number or types of arguments.) | |
21 | Obviously, we can't write tests for those cases here. However, for each | |
22 | intrinsics, there is a corresponding macro in `stdlib` which wraps the | |
23 | intrinsics, and is named the same except omitting the `@`. These wrappers | |
24 | give the intrinsics predictable failure modes in these cases, by raising | |
25 | defined exceptions. | |
26 | ||
27 | ### `@prepend` ### | |
28 | ||
29 | `@prepend` evaluates both of its arguments, then evaluates to a list cell | |
30 | which contains the first value as its data and the second value as the | |
31 | continuation of the list. | |
32 | ||
33 | | (display | |
34 | | (@prepend () ())) | |
35 | = (()) | |
36 | ||
37 | | (display | |
38 | | (@prepend #t (@prepend #f ()))) | |
39 | = (#t #f) | |
40 | ||
41 | `@prepend` expects exactly two arguments. The first may be of any type. | |
42 | The second`@prepend` must be a list. If these conditions are not met, | |
43 | the behaviour is undefined. | |
44 | ||
45 | `@prepend` is basically equivalent to Scheme's `cons`, except for the | |
46 | requirement that the second argument be a list. | |
47 | ||
48 | ### `@head` ### | |
49 | ||
50 | `@head` evaluates its argument to a list, and evaluates to the first element | |
51 | of that list. | |
52 | ||
53 | | (display | |
54 | | (@head (@prepend #t ()))) | |
55 | = #t | |
56 | ||
57 | `@head` expects exactly one argument, and expects it to be a list. | |
58 | If these conditions are not met, the behaviour is undefined. | |
59 | ||
60 | `@head` is basically equivalent to Scheme's `car`. | |
61 | ||
62 | ### `@tail` ### | |
63 | ||
64 | `@tail` evaluates its argument to a list, and evaluates to the tail of that | |
65 | list (the sublist obtained by removing the first element.) | |
66 | ||
67 | | (display | |
68 | | (@tail (@prepend #t (@prepend #f ())))) | |
69 | = (#f) | |
70 | ||
71 | `@tail` expects exactly one argument, and expects it to be a list. | |
72 | If these conditions are not met, the behaviour is undefined. | |
73 | ||
74 | `@tail` is basically equivalent to Scheme's `cdr`. | |
75 | ||
76 | ### `@if` ### | |
77 | ||
78 | `@if` evaluates its first argument to a boolean value. If that value is | |
79 | `#t`, it evaluates, and evaluates to, its second argument; or if that value | |
80 | is `#f` it evaluates, and evaluates to, its third argument. In all cases, | |
81 | at most two arguments are evaluated. | |
82 | ||
83 | | (display | |
84 | | (@if #t 7 9)) | |
85 | = 7 | |
86 | ||
87 | | (display | |
88 | | (@if #f 7 9)) | |
89 | = 9 | |
90 | ||
91 | The identifiers named in the branch which is not evaluated need not be | |
92 | properly bound to values in the environment. | |
93 | ||
94 | | (display | |
95 | | (@if #t 1 (prepend fred ethel))) | |
96 | = 1 | |
97 | ||
98 | The second and third arguments can be arbitrary expressions, but `@if` | |
99 | expects its first argument to be a boolean. `@if` expects exactly three | |
100 | arguments. If these conditions are not met, the behaviour is undefined. | |
101 | ||
102 | `@if` is basically equivalent to Scheme's `if`. | |
103 | ||
104 | ### `@equal?` ### | |
105 | ||
106 | `@equal?` evaluates both of its arguments to arbitrary S-expressions | |
107 | and compares them for deep equality. | |
108 | ||
109 | `@equal?` works on symbols. | |
110 | ||
111 | | (define literal (@macro (s a e) (@head a))) | |
112 | | (display | |
113 | | (@equal? | |
114 | | (literal this-symbol) | |
115 | | (literal this-symbol))) | |
116 | = #t | |
117 | ||
118 | | (define literal (@macro (s a e) (@head a))) | |
119 | | (display | |
120 | | (@equal? | |
121 | | (literal this-symbol) | |
122 | | (literal that-symbol))) | |
123 | = #f | |
124 | ||
125 | `@equal?` works on lists. | |
126 | ||
127 | | (display | |
128 | | (@equal? (@prepend 1 (@prepend 2 (@prepend 3 ()))) | |
129 | | (@prepend 1 (@prepend 2 (@prepend 3 ()))))) | |
130 | = #t | |
131 | ||
132 | `@equal?` works on lists, deeply. | |
133 | ||
134 | | (display | |
135 | | (@equal? (@prepend 1 (@prepend 2 (@prepend 7 ()))) | |
136 | | (@prepend 1 (@prepend 2 (@prepend 3 ()))))) | |
137 | = #f | |
138 | ||
139 | Two values of different types are never equal. | |
140 | ||
141 | | (define literal (@macro (s a e) (@head a))) | |
142 | | (display | |
143 | | (@equal? #t | |
144 | | (@prepend (literal a) ()))) | |
145 | = #f | |
146 | ||
147 | | (display | |
148 | | (@equal? #f | |
149 | | ())) | |
150 | = #f | |
151 | ||
152 | `@equal?` expects exactly two arguments, of any type. | |
153 | ||
154 | ### `@list?` ### | |
155 | ||
156 | `@list?` evaluates its argument, then evaluates to `#t` if it is a list, | |
157 | `#f` otherwise. | |
158 | ||
159 | | (define literal (@macro (s a e) (@head a))) | |
160 | | (display | |
161 | | (@list? (literal (a b)))) | |
162 | = #t | |
163 | ||
164 | | (define literal (@macro (s a e) (@head a))) | |
165 | | (display | |
166 | | (@list? (literal (a b c d e f)))) | |
167 | = #t | |
168 | ||
169 | | (display | |
170 | | (@list? (@prepend 4 (@prepend 5 ())))) | |
171 | = #t | |
172 | ||
173 | The empty list is a list. | |
174 | ||
175 | | (display | |
176 | | (@list? ())) | |
177 | = #t | |
178 | ||
179 | Symbols are not lists. | |
180 | ||
181 | | (define literal (@macro (s a e) (@head a))) | |
182 | | (display | |
183 | | (@list? (literal a))) | |
184 | = #f | |
185 | ||
186 | The argument to `@list?` may (naturally) be any type, but there must be | |
187 | exactly one argument. | |
188 | ||
189 | ### `@macro?` ### | |
190 | ||
191 | `@macro?` evaluates its argument, then evaluates to `#t` if it is a macro, | |
192 | or `#f` if it is not. | |
193 | ||
194 | | (display | |
195 | | (@macro? (@macro (self args env) args))) | |
196 | = #t | |
197 | ||
198 | TODO: this should probably be false. Intrinsics are slightly different | |
199 | from macros. Either that, or, it should be, like `@applyable?`, or | |
200 | something. | |
201 | ||
202 | | (display | |
203 | | (@macro? @macro)) | |
204 | = #t | |
205 | ||
206 | | (display | |
207 | | (@macro? ((@macro (self args env) (@head args)) @macro))) | |
208 | = #f | |
209 | ||
210 | | (display | |
211 | | (@macro? 5)) | |
212 | = #f | |
213 | ||
214 | The argument to `@macro?` may (naturally) be any type, but there must be | |
215 | exactly one argument. | |
216 | ||
217 | ### `@symbol?` ### | |
218 | ||
219 | `@symbol?` evaluates its argument, then evaluates to `#t` if it is a symbol, | |
220 | `#f` otherwise. | |
221 | ||
222 | | (define literal (@macro (s a e) (@head a))) | |
223 | | (display | |
224 | | (@symbol? (literal this-symbol))) | |
225 | = #t | |
226 | ||
227 | Numbers are not symbols. | |
228 | ||
229 | | (display | |
230 | | (@symbol? 9)) | |
231 | = #f | |
232 | ||
233 | Lists are not symbols. | |
234 | ||
235 | | (display | |
236 | | (@symbol? (@prepend 1 ()))) | |
237 | = #f | |
238 | ||
239 | The argument to `@symbol?` may (naturally) be any type, but there must be | |
240 | exactly one argument. | |
241 | ||
242 | ### `@number?` ### | |
243 | ||
244 | `@number?` evaluates its argument, then evaluates to `#t` if it is a | |
245 | number, `#f` otherwise. | |
246 | ||
247 | | (display | |
248 | | (@number? 7)) | |
249 | = #t | |
250 | ||
251 | | (display | |
252 | | (@number? 0)) | |
253 | = #t | |
254 | ||
255 | | (display | |
256 | | (@number? ())) | |
257 | = #f | |
258 | ||
259 | | (display | |
260 | | (@number? #t)) | |
261 | = #f | |
262 | ||
263 | | (define literal (@macro (s a e) (@head a))) | |
264 | | (display | |
265 | | (@number? (literal seven))) | |
266 | = #f | |
267 | ||
268 | That's a good question... | |
269 | ||
270 | | (define literal (@macro (s a e) (@head a))) | |
271 | | (display | |
272 | | (@number? (literal 7))) | |
273 | = #t | |
274 | ||
275 | The argument to `@number?` may (naturally) be any type, but there must be | |
276 | exactly one argument. | |
277 | ||
278 | ### `@subtract` ### | |
279 | ||
280 | `@subtract` evaluates its first argument to a number, then | |
281 | evaluates its second argument to a number, then evaluates | |
282 | to the difference between the first and second numbers. | |
283 | ||
284 | | (display | |
285 | | (@subtract 6 4)) | |
286 | = 2 | |
287 | ||
288 | | (display | |
289 | | (@subtract 1000 8000)) | |
290 | = -7000 | |
291 | ||
292 | Addition may be accomplished by negating the second argument. | |
293 | ||
294 | | (display | |
295 | | (@subtract 999 (@subtract 0 999))) | |
296 | = 1998 | |
297 | ||
298 | `@subtract` expects both of its arguments to be numbers. | |
299 | ||
300 | `@subtract` expects exactly two arguments. | |
301 | ||
302 | ### `@sign` ### | |
303 | ||
304 | `@sign` evaluates its sole argument to a number, then | |
305 | evaluates to 0 if that number is 0, 1 if that number is positive, or | |
306 | -1 if that number is negative. | |
307 | ||
308 | | (display | |
309 | | (@sign 26)) | |
310 | = 1 | |
311 | ||
312 | | (display | |
313 | | (@sign 0)) | |
314 | = 0 | |
315 | ||
316 | | (display | |
317 | | (@sign (@subtract 0 200))) | |
318 | = -1 | |
319 | ||
320 | `@sign` expects exactly one argument. | |
321 | ||
322 | ### `@eval` ### | |
323 | ||
324 | `@eval` evaluates its first argument to obtain an environment, then | |
325 | evaluates its second argument to obtain an S-expression; it then | |
326 | evaluates that S-expression in the given environment. | |
327 | ||
328 | | (define literal (@macro (s a e) (@head a))) | |
329 | | (define env (@macro (s a e) e)) | |
330 | | (display | |
331 | | (@eval (env) (literal | |
332 | | (@prepend (literal a) | |
333 | | (@prepend (literal b) ()))))) | |
334 | = (a b) | |
335 | ||
336 | | (define literal (@macro (s a e) (@head a))) | |
337 | | (display | |
338 | | (@eval () (literal | |
339 | | (@prepend (literal a) | |
340 | | (@prepend (literal b) ()))))) | |
341 | ? uncaught exception: (unbound-identifier @prepend) | |
342 | ||
343 | Something fairly complicated that uses `bind`...? | |
344 | ||
345 | | (define literal (@macro (s a e) (@head a))) | |
346 | | (define bind (@macro (self args env) | |
347 | | (@eval | |
348 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
349 | | (@head (@tail (@tail args)))))) | |
350 | | (display | |
351 | | (bind bindings (@prepend | |
352 | | (@prepend (literal same) (@prepend @equal? ())) | |
353 | | (@prepend | |
354 | | (@prepend (literal x) (@prepend #f ())) | |
355 | | ())) | |
356 | | (@eval bindings (literal (same x x))))) | |
357 | = #t | |
358 | ||
359 | If two bindings for the same identifier are supplied in the environment | |
360 | alist passed to `@eval`, the one closer to the front of the alist takes | |
361 | precedence. | |
362 | ||
363 | | (define literal (@macro (s a e) (@head a))) | |
364 | | (define bind (@macro (self args env) | |
365 | | (@eval | |
366 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
367 | | (@head (@tail (@tail args)))))) | |
368 | | (display | |
369 | | (bind bindings (@prepend | |
370 | | (@prepend (literal foo) (@prepend (literal yes) ())) | |
371 | | (@prepend | |
372 | | (@prepend (literal foo) (@prepend (literal no) ())) | |
373 | | ())) | |
374 | | (@eval bindings (literal foo)))) | |
375 | = yes | |
376 | ||
377 | ### `@macro` ### | |
378 | ||
379 | `@macro` takes its first argument to be a list of three formal | |
380 | parameters, and its second argument to be an arbitrary expression, | |
381 | and uses these two arguments to build, and evaluate to, a macro | |
382 | value. | |
383 | ||
384 | When this macro value is evaluated, the first formal argument will | |
385 | be bound to the macro itself, the second will be bound to the | |
386 | literal, unevaluated list of arguments passed to the macro, and the | |
387 | third will be bound to an alist representing the environment in | |
388 | effect at the point the macro value is evaluated. | |
389 | ||
390 | These formals are conventionally called `self`, `args`, and `env`, | |
391 | but different names can be chosen in the `macro` definition, for | |
392 | instance to avoid shadowing. | |
393 | ||
394 | `literal`, in fact, can be defined as a macro, and it is one of the | |
395 | simplest possible macros that can be written: | |
396 | ||
397 | | (display | |
398 | | ((@macro (self args env) (@head args)) (why hello there))) | |
399 | = (why hello there) | |
400 | ||
401 | And when we want to use it in the tests, we'll define it first, like | |
402 | this: | |
403 | ||
404 | (define literal (@macro (s a e) (@head a))) | |
405 | ||
406 | Another facility that can be defined simply by a macro is `env`, | |
407 | and we'll define it like this: | |
408 | ||
409 | (define env (@macro (s a e) e)) | |
410 | ||
411 | Macros have "closure" behavior; that is, bindings in force when a | |
412 | macro is defined will still be in force when the macro is applied, | |
413 | even if they are no longer lexically in scope. (Please try to ignore | |
414 | the heavy `define`s that are used in this test...) | |
415 | ||
416 | | (define literal (@macro (s a e) (@head a))) | |
417 | | (define bind (@macro (self args env) | |
418 | | (@eval | |
419 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
420 | | (@head (@tail (@tail args)))))) | |
421 | | (define let (@macro (self args env) | |
422 | | (bind bindings (@head args) | |
423 | | (@if (@equal? bindings ()) | |
424 | | (@eval env (@head (@tail args))) | |
425 | | (bind binding (@head bindings) | |
426 | | (bind name (@head binding) | |
427 | | (@if (@symbol? name) | |
428 | | (bind value (@eval env (@head (@tail binding))) | |
429 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
430 | | (bind newbindings (@tail bindings) | |
431 | | (bind newargs (@prepend newbindings (@tail args)) | |
432 | | (@eval newenv (@prepend self newargs)))))) | |
433 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
434 | | (display | |
435 | | ((let | |
436 | | ((a (literal these-are)) | |
437 | | (m (@macro (self args env) (@prepend a args)))) | |
438 | | m) my args)) | |
439 | = (these-are my args) | |
440 | ||
441 | Macros can return macros. | |
442 | ||
443 | | (define literal (@macro (s a e) (@head a))) | |
444 | | (define bind (@macro (self args env) | |
445 | | (@eval | |
446 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
447 | | (@head (@tail (@tail args)))))) | |
448 | | (define let (@macro (self args env) | |
449 | | (bind bindings (@head args) | |
450 | | (@if (@equal? bindings ()) | |
451 | | (@eval env (@head (@tail args))) | |
452 | | (bind binding (@head bindings) | |
453 | | (bind name (@head binding) | |
454 | | (@if (@symbol? name) | |
455 | | (bind value (@eval env (@head (@tail binding))) | |
456 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
457 | | (bind newbindings (@tail bindings) | |
458 | | (bind newargs (@prepend newbindings (@tail args)) | |
459 | | (@eval newenv (@prepend self newargs)))))) | |
460 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
461 | | (display | |
462 | | (let | |
463 | | ((mk (@macro (self argsa env) | |
464 | | (@macro (self argsb env) | |
465 | | (@prepend (@head argsb) argsa)))) | |
466 | | (mk2 (mk vindaloo))) | |
467 | | (mk2 chicken))) | |
468 | = (chicken vindaloo) | |
469 | ||
470 | Arguments to macros shadow any other bindings in effect. | |
471 | ||
472 | | (define literal (@macro (s a e) (@head a))) | |
473 | | (define bind (@macro (self args env) | |
474 | | (@eval | |
475 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
476 | | (@head (@tail (@tail args)))))) | |
477 | | (define let (@macro (self args env) | |
478 | | (bind bindings (@head args) | |
479 | | (@if (@equal? bindings ()) | |
480 | | (@eval env (@head (@tail args))) | |
481 | | (bind binding (@head bindings) | |
482 | | (bind name (@head binding) | |
483 | | (@if (@symbol? name) | |
484 | | (bind value (@eval env (@head (@tail binding))) | |
485 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
486 | | (bind newbindings (@tail bindings) | |
487 | | (bind newargs (@prepend newbindings (@tail args)) | |
488 | | (@eval newenv (@prepend self newargs)))))) | |
489 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
490 | | (display | |
491 | | (let | |
492 | | ((args (literal a)) | |
493 | | (b (@macro (self args env) (@prepend args args)))) | |
494 | | (b 7))) | |
495 | = ((7) 7) | |
496 | ||
497 | `self` is there to let you write recursive macros. The following | |
498 | example demonstrates this; it evaluates `(prepend b d)` in an environment | |
499 | where all the identifiers you list after `qqq` have been bound to 0. | |
500 | ||
501 | | (define literal (@macro (s a e) (@head a))) | |
502 | | (define bind (@macro (self args env) | |
503 | | (@eval | |
504 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
505 | | (@head (@tail (@tail args)))))) | |
506 | | (display | |
507 | | (bind qqq | |
508 | | (@macro (self args env) | |
509 | | (@if (@equal? args ()) | |
510 | | (@eval env (literal (@prepend b (@prepend d ())))) | |
511 | | (@eval (@prepend (@prepend (@head args) (@prepend 0 ())) env) | |
512 | | (@prepend self (@tail args))))) | |
513 | | (bind b 1 (bind d 4 (qqq b c d))))) | |
514 | = (0 0) | |
515 | ||
516 | | (define literal (@macro (s a e) (@head a))) | |
517 | | (define bind (@macro (self args env) | |
518 | | (@eval | |
519 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
520 | | (@head (@tail (@tail args)))))) | |
521 | | (display | |
522 | | (bind qqq | |
523 | | (@macro (self args env) | |
524 | | (@if (@equal? args ()) | |
525 | | (@eval env (literal (@prepend b (@prepend d ())))) | |
526 | | (@eval (@prepend (@prepend (@head args) (@prepend 0 ())) env) | |
527 | | (@prepend self (@tail args))))) | |
528 | | (bind b 1 (bind d 4 (qqq x y z))))) | |
529 | = (1 4) | |
530 | ||
531 | Your recursive `macro` application doesn't have to be tail-recursive. | |
532 | ||
533 | | (define literal (@macro (s a e) (@head a))) | |
534 | | (define bind (@macro (self args env) | |
535 | | (@eval | |
536 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
537 | | (@head (@tail (@tail args)))))) | |
538 | | (display | |
539 | | (bind make-env | |
540 | | (@macro (self args env) | |
541 | | (@if (@equal? args ()) | |
542 | | () | |
543 | | (@prepend (@prepend (@head args) | |
544 | | (@prepend (@eval env (@head args)) ())) | |
545 | | (@eval env | |
546 | | (@prepend self (@tail args)))))) | |
547 | | (bind b 1 (bind d 4 (make-env b d @macro))))) | |
548 | = ((b 1) (d 4) (@macro @macro)) | |
549 | ||
550 | `@macro` expects exactly two arguments. | |
551 | ||
552 | `@macro` expects its first argument to be a list of exactly three | |
553 | symbols. | |
554 | ||
555 | ### `@raise` ### | |
556 | ||
557 | `@raise` evaluates its argument to obtain a value, then raises an | |
558 | exception with that value. | |
559 | ||
560 | If no exception handlers have been installed in the execution | |
561 | history, the Robin program will terminate with an error, ceasing execution | |
562 | of all Robin processes immediately, returning control to the operating | |
563 | system. For the sake of usability, the error should include a message which | |
564 | refers to the exception that triggered it, but this is not a strict | |
565 | requirement. | |
566 | ||
567 | | (display | |
568 | | (@raise 999999)) | |
569 | ? uncaught exception: 999999 | |
570 | ||
571 | `@raise`'s single argument may be any kind of value, but `raise` expects | |
572 | exactly one argument. | |
573 | ||
574 | A Robin environment may install exception handlers which are not defined | |
575 | in Robin code itself. (i.e. exiting to the operating system is not a | |
576 | strict requirement.) | |
577 | ||
578 | ### `@catch` ### | |
579 | ||
580 | `@catch` installs an exception handler. | |
581 | ||
582 | If an exception is raised when evaluating the final argument of | |
583 | `@catch`, the exception value is bound to the symbol given as the | |
584 | first argument of `@catch`, and the second argument of `@catch` is | |
585 | evaluated in that new environment. | |
586 | ||
587 | | (define literal (@macro (s a e) (@head a))) | |
588 | | (define list (@macro (self args env) | |
589 | | (@if (@equal? args ()) | |
590 | | () | |
591 | | (@prepend (@eval env (@head args)) | |
592 | | (@eval env (@prepend self (@tail args))))))) | |
593 | | (display | |
594 | | (@catch error (list error #f) | |
595 | | (@raise (literal (nasty-value 999999))))) | |
596 | = ((nasty-value 999999) #f) | |
597 | ||
598 | `@catch` *cannot necessarily* catch exceptions raised by intrinsics. | |
599 | It ought to be able to catch exceptions raised by intrinsics wrappers, | |
600 | though. | |
601 | ||
602 | The innermost `@catch` will catch the exception. | |
603 | ||
604 | | (define literal (@macro (s a e) (@head a))) | |
605 | | (define list (@macro (self args env) | |
606 | | (@if (@equal? args ()) | |
607 | | () | |
608 | | (@prepend (@eval env (@head args)) | |
609 | | (@eval env (@prepend self (@tail args))))))) | |
610 | | (display | |
611 | | (@catch error (list error 5) | |
612 | | (@catch error (list error 9) | |
613 | | (@raise (literal derpy-value))))) | |
614 | = (derpy-value 9) | |
615 | ||
616 | An exception raised from within an exception handler is | |
617 | caught by the next innermost exception handler. | |
618 | ||
619 | | (define list (@macro (self args env) | |
620 | | (@if (@equal? args ()) | |
621 | | () | |
622 | | (@prepend (@eval env (@head args)) | |
623 | | (@eval env (@prepend self (@tail args))))))) | |
624 | | (display | |
625 | | (@catch error (list error 5) | |
626 | | (@catch error (list error 9) | |
627 | | (@catch error (@raise (list error error)) | |
628 | | (@raise 7))))) | |
629 | = ((7 7) 9) | |
630 | ||
631 | `@catch` expects its first argument to be an identifier. | |
632 | ||
633 | `@catch` expects exactly three arguments. | |
634 | ||
635 | TODO we should probably have some tests that prove that `@catch` can | |
636 | catch errors raised from inside macros. |
0 | Robin Intrinsics | |
1 | ================ | |
2 | ||
3 | -> Tests for functionality "Interpret core Robin Program" | |
4 | ||
5 | An _intrinsic_ is one of the data types in Robin. It is like a macro, except | |
6 | that it is implemented intrinsically (and thus does not support quite | |
7 | every operation that is supported on macros, for example, examining its | |
8 | internals.) | |
9 | ||
10 | Robin provides (as of this writing) 15 intrinsics. These represent | |
11 | the fundamental functionality that is used to evaluate programs, and that | |
12 | cannot be expressed as macros written in Robin (not without resorting to | |
13 | meta-circularity, at any rate.) All other macros are built up on top of | |
14 | the intrinsics. | |
15 | ||
16 | This set of intrinsics is not optional — every Robin implementation must | |
17 | provide them, or it's not Robin. | |
18 | ||
19 | Intrinsics usually have undefined behaviour if their preconditions are | |
20 | not met (i.e., if they are called with wrong number or types of arguments.) | |
21 | Obviously, we can't write tests for those cases here. However, for each | |
22 | intrinsics, there is a corresponding macro in `stdlib` which wraps the | |
23 | intrinsics, and is named the same except omitting the `@`. These wrappers | |
24 | give the intrinsics predictable failure modes in these cases, by raising | |
25 | defined exceptions. | |
26 | ||
27 | ### `@prepend` ### | |
28 | ||
29 | `@prepend` evaluates both of its arguments, then evaluates to a list cell | |
30 | which contains the first value as its data and the second value as the | |
31 | continuation of the list. | |
32 | ||
33 | | (display | |
34 | | (@prepend () ())) | |
35 | = (()) | |
36 | ||
37 | | (display | |
38 | | (@prepend #t (@prepend #f ()))) | |
39 | = (#t #f) | |
40 | ||
41 | `@prepend` expects exactly two arguments. The first may be of any type. | |
42 | The second`@prepend` must be a list. If these conditions are not met, | |
43 | the behaviour is undefined. | |
44 | ||
45 | `@prepend` is basically equivalent to Scheme's `cons`, except for the | |
46 | requirement that the second argument be a list. | |
47 | ||
48 | ### `@head` ### | |
49 | ||
50 | `@head` evaluates its argument to a list, and evaluates to the first element | |
51 | of that list. | |
52 | ||
53 | | (display | |
54 | | (@head (@prepend #t ()))) | |
55 | = #t | |
56 | ||
57 | `@head` expects exactly one argument, and expects it to be a list. | |
58 | If these conditions are not met, the behaviour is undefined. | |
59 | ||
60 | `@head` is basically equivalent to Scheme's `car`. | |
61 | ||
62 | ### `@tail` ### | |
63 | ||
64 | `@tail` evaluates its argument to a list, and evaluates to the tail of that | |
65 | list (the sublist obtained by removing the first element.) | |
66 | ||
67 | | (display | |
68 | | (@tail (@prepend #t (@prepend #f ())))) | |
69 | = (#f) | |
70 | ||
71 | `@tail` expects exactly one argument, and expects it to be a list. | |
72 | If these conditions are not met, the behaviour is undefined. | |
73 | ||
74 | `@tail` is basically equivalent to Scheme's `cdr`. | |
75 | ||
76 | ### `@if` ### | |
77 | ||
78 | `@if` evaluates its first argument to a boolean value. If that value is | |
79 | `#t`, it evaluates, and evaluates to, its second argument; or if that value | |
80 | is `#f` it evaluates, and evaluates to, its third argument. In all cases, | |
81 | at most two arguments are evaluated. | |
82 | ||
83 | | (display | |
84 | | (@if #t 7 9)) | |
85 | = 7 | |
86 | ||
87 | | (display | |
88 | | (@if #f 7 9)) | |
89 | = 9 | |
90 | ||
91 | The identifiers named in the branch which is not evaluated need not be | |
92 | properly bound to values in the environment. | |
93 | ||
94 | | (display | |
95 | | (@if #t 1 (prepend fred ethel))) | |
96 | = 1 | |
97 | ||
98 | The second and third arguments can be arbitrary expressions, but `@if` | |
99 | expects its first argument to be a boolean. `@if` expects exactly three | |
100 | arguments. If these conditions are not met, the behaviour is undefined. | |
101 | ||
102 | `@if` is basically equivalent to Scheme's `if`. | |
103 | ||
104 | ### `@equal?` ### | |
105 | ||
106 | `@equal?` evaluates both of its arguments to arbitrary S-expressions | |
107 | and compares them for deep equality. | |
108 | ||
109 | `@equal?` works on symbols. | |
110 | ||
111 | | (define literal (@macro (s a e) (@head a))) | |
112 | | (display | |
113 | | (@equal? | |
114 | | (literal this-symbol) | |
115 | | (literal this-symbol))) | |
116 | = #t | |
117 | ||
118 | | (define literal (@macro (s a e) (@head a))) | |
119 | | (display | |
120 | | (@equal? | |
121 | | (literal this-symbol) | |
122 | | (literal that-symbol))) | |
123 | = #f | |
124 | ||
125 | `@equal?` works on lists. | |
126 | ||
127 | | (display | |
128 | | (@equal? (@prepend 1 (@prepend 2 (@prepend 3 ()))) | |
129 | | (@prepend 1 (@prepend 2 (@prepend 3 ()))))) | |
130 | = #t | |
131 | ||
132 | `@equal?` works on lists, deeply. | |
133 | ||
134 | | (display | |
135 | | (@equal? (@prepend 1 (@prepend 2 (@prepend 7 ()))) | |
136 | | (@prepend 1 (@prepend 2 (@prepend 3 ()))))) | |
137 | = #f | |
138 | ||
139 | Two values of different types are never equal. | |
140 | ||
141 | | (define literal (@macro (s a e) (@head a))) | |
142 | | (display | |
143 | | (@equal? #t | |
144 | | (@prepend (literal a) ()))) | |
145 | = #f | |
146 | ||
147 | | (display | |
148 | | (@equal? #f | |
149 | | ())) | |
150 | = #f | |
151 | ||
152 | `@equal?` expects exactly two arguments, of any type. | |
153 | ||
154 | ### `@list?` ### | |
155 | ||
156 | `@list?` evaluates its argument, then evaluates to `#t` if it is a list, | |
157 | `#f` otherwise. | |
158 | ||
159 | | (define literal (@macro (s a e) (@head a))) | |
160 | | (display | |
161 | | (@list? (literal (a b)))) | |
162 | = #t | |
163 | ||
164 | | (define literal (@macro (s a e) (@head a))) | |
165 | | (display | |
166 | | (@list? (literal (a b c d e f)))) | |
167 | = #t | |
168 | ||
169 | | (display | |
170 | | (@list? (@prepend 4 (@prepend 5 ())))) | |
171 | = #t | |
172 | ||
173 | The empty list is a list. | |
174 | ||
175 | | (display | |
176 | | (@list? ())) | |
177 | = #t | |
178 | ||
179 | Symbols are not lists. | |
180 | ||
181 | | (define literal (@macro (s a e) (@head a))) | |
182 | | (display | |
183 | | (@list? (literal a))) | |
184 | = #f | |
185 | ||
186 | The argument to `@list?` may (naturally) be any type, but there must be | |
187 | exactly one argument. | |
188 | ||
189 | ### `@macro?` ### | |
190 | ||
191 | `@macro?` evaluates its argument, then evaluates to `#t` if it is a macro, | |
192 | or `#f` if it is not. | |
193 | ||
194 | | (display | |
195 | | (@macro? (@macro (self args env) args))) | |
196 | = #t | |
197 | ||
198 | TODO: this should probably be false. Intrinsics are slightly different | |
199 | from macros. Either that, or, it should be, like `@applyable?`, or | |
200 | something. | |
201 | ||
202 | | (display | |
203 | | (@macro? @macro)) | |
204 | = #t | |
205 | ||
206 | | (display | |
207 | | (@macro? ((@macro (self args env) (@head args)) @macro))) | |
208 | = #f | |
209 | ||
210 | | (display | |
211 | | (@macro? 5)) | |
212 | = #f | |
213 | ||
214 | The argument to `@macro?` may (naturally) be any type, but there must be | |
215 | exactly one argument. | |
216 | ||
217 | ### `@symbol?` ### | |
218 | ||
219 | `@symbol?` evaluates its argument, then evaluates to `#t` if it is a symbol, | |
220 | `#f` otherwise. | |
221 | ||
222 | | (define literal (@macro (s a e) (@head a))) | |
223 | | (display | |
224 | | (@symbol? (literal this-symbol))) | |
225 | = #t | |
226 | ||
227 | Numbers are not symbols. | |
228 | ||
229 | | (display | |
230 | | (@symbol? 9)) | |
231 | = #f | |
232 | ||
233 | Lists are not symbols. | |
234 | ||
235 | | (display | |
236 | | (@symbol? (@prepend 1 ()))) | |
237 | = #f | |
238 | ||
239 | The argument to `@symbol?` may (naturally) be any type, but there must be | |
240 | exactly one argument. | |
241 | ||
242 | ### `@number?` ### | |
243 | ||
244 | `@number?` evaluates its argument, then evaluates to `#t` if it is a | |
245 | number, `#f` otherwise. | |
246 | ||
247 | | (display | |
248 | | (@number? 7)) | |
249 | = #t | |
250 | ||
251 | | (display | |
252 | | (@number? 0)) | |
253 | = #t | |
254 | ||
255 | | (display | |
256 | | (@number? ())) | |
257 | = #f | |
258 | ||
259 | | (display | |
260 | | (@number? #t)) | |
261 | = #f | |
262 | ||
263 | | (define literal (@macro (s a e) (@head a))) | |
264 | | (display | |
265 | | (@number? (literal seven))) | |
266 | = #f | |
267 | ||
268 | That's a good question... | |
269 | ||
270 | | (define literal (@macro (s a e) (@head a))) | |
271 | | (display | |
272 | | (@number? (literal 7))) | |
273 | = #t | |
274 | ||
275 | The argument to `@number?` may (naturally) be any type, but there must be | |
276 | exactly one argument. | |
277 | ||
278 | ### `@subtract` ### | |
279 | ||
280 | `@subtract` evaluates its first argument to a number, then | |
281 | evaluates its second argument to a number, then evaluates | |
282 | to the difference between the first and second numbers. | |
283 | ||
284 | | (display | |
285 | | (@subtract 6 4)) | |
286 | = 2 | |
287 | ||
288 | | (display | |
289 | | (@subtract 1000 8000)) | |
290 | = -7000 | |
291 | ||
292 | Addition may be accomplished by negating the second argument. | |
293 | ||
294 | | (display | |
295 | | (@subtract 999 (@subtract 0 999))) | |
296 | = 1998 | |
297 | ||
298 | `@subtract` expects both of its arguments to be numbers. | |
299 | ||
300 | `@subtract` expects exactly two arguments. | |
301 | ||
302 | ### `@sign` ### | |
303 | ||
304 | `@sign` evaluates its sole argument to a number, then | |
305 | evaluates to 0 if that number is 0, 1 if that number is positive, or | |
306 | -1 if that number is negative. | |
307 | ||
308 | | (display | |
309 | | (@sign 26)) | |
310 | = 1 | |
311 | ||
312 | | (display | |
313 | | (@sign 0)) | |
314 | = 0 | |
315 | ||
316 | | (display | |
317 | | (@sign (@subtract 0 200))) | |
318 | = -1 | |
319 | ||
320 | `@sign` expects exactly one argument. | |
321 | ||
322 | ### `@eval` ### | |
323 | ||
324 | `@eval` evaluates its first argument to obtain an environment, then | |
325 | evaluates its second argument to obtain an S-expression; it then | |
326 | evaluates that S-expression in the given environment. | |
327 | ||
328 | | (define literal (@macro (s a e) (@head a))) | |
329 | | (define env (@macro (s a e) e)) | |
330 | | (display | |
331 | | (@eval (env) (literal | |
332 | | (@prepend (literal a) | |
333 | | (@prepend (literal b) ()))))) | |
334 | = (a b) | |
335 | ||
336 | | (define literal (@macro (s a e) (@head a))) | |
337 | | (display | |
338 | | (@eval () (literal | |
339 | | (@prepend (literal a) | |
340 | | (@prepend (literal b) ()))))) | |
341 | ? uncaught exception: (unbound-identifier @prepend) | |
342 | ||
343 | Something fairly complicated that uses `bind`...? | |
344 | ||
345 | | (define literal (@macro (s a e) (@head a))) | |
346 | | (define bind (@macro (self args env) | |
347 | | (@eval | |
348 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
349 | | (@head (@tail (@tail args)))))) | |
350 | | (display | |
351 | | (bind bindings (@prepend | |
352 | | (@prepend (literal same) (@prepend @equal? ())) | |
353 | | (@prepend | |
354 | | (@prepend (literal x) (@prepend #f ())) | |
355 | | ())) | |
356 | | (@eval bindings (literal (same x x))))) | |
357 | = #t | |
358 | ||
359 | If two bindings for the same identifier are supplied in the environment | |
360 | alist passed to `@eval`, the one closer to the front of the alist takes | |
361 | precedence. | |
362 | ||
363 | | (define literal (@macro (s a e) (@head a))) | |
364 | | (define bind (@macro (self args env) | |
365 | | (@eval | |
366 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
367 | | (@head (@tail (@tail args)))))) | |
368 | | (display | |
369 | | (bind bindings (@prepend | |
370 | | (@prepend (literal foo) (@prepend (literal yes) ())) | |
371 | | (@prepend | |
372 | | (@prepend (literal foo) (@prepend (literal no) ())) | |
373 | | ())) | |
374 | | (@eval bindings (literal foo)))) | |
375 | = yes | |
376 | ||
377 | ### `@macro` ### | |
378 | ||
379 | `@macro` takes its first argument to be a list of three formal | |
380 | parameters, and its second argument to be an arbitrary expression, | |
381 | and uses these two arguments to build, and evaluate to, a macro | |
382 | value. | |
383 | ||
384 | When this macro value is evaluated, the first formal argument will | |
385 | be bound to the macro itself, the second will be bound to the | |
386 | literal, unevaluated list of arguments passed to the macro, and the | |
387 | third will be bound to an alist representing the environment in | |
388 | effect at the point the macro value is evaluated. | |
389 | ||
390 | These formals are conventionally called `self`, `args`, and `env`, | |
391 | but different names can be chosen in the `macro` definition, for | |
392 | instance to avoid shadowing. | |
393 | ||
394 | `literal`, in fact, can be defined as a macro, and it is one of the | |
395 | simplest possible macros that can be written: | |
396 | ||
397 | | (display | |
398 | | ((@macro (self args env) (@head args)) (why hello there))) | |
399 | = (why hello there) | |
400 | ||
401 | And when we want to use it in the tests, we'll define it first, like | |
402 | this: | |
403 | ||
404 | (define literal (@macro (s a e) (@head a))) | |
405 | ||
406 | Another facility that can be defined simply by a macro is `env`, | |
407 | and we'll define it like this: | |
408 | ||
409 | (define env (@macro (s a e) e)) | |
410 | ||
411 | Macros have "closure" behavior; that is, bindings in force when a | |
412 | macro is defined will still be in force when the macro is applied, | |
413 | even if they are no longer lexically in scope. (Please try to ignore | |
414 | the heavy `define`s that are used in this test...) | |
415 | ||
416 | | (define literal (@macro (s a e) (@head a))) | |
417 | | (define bind (@macro (self args env) | |
418 | | (@eval | |
419 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
420 | | (@head (@tail (@tail args)))))) | |
421 | | (define let (@macro (self args env) | |
422 | | (bind bindings (@head args) | |
423 | | (@if (@equal? bindings ()) | |
424 | | (@eval env (@head (@tail args))) | |
425 | | (bind binding (@head bindings) | |
426 | | (bind name (@head binding) | |
427 | | (@if (@symbol? name) | |
428 | | (bind value (@eval env (@head (@tail binding))) | |
429 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
430 | | (bind newbindings (@tail bindings) | |
431 | | (bind newargs (@prepend newbindings (@tail args)) | |
432 | | (@eval newenv (@prepend self newargs)))))) | |
433 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
434 | | (display | |
435 | | ((let | |
436 | | ((a (literal these-are)) | |
437 | | (m (@macro (self args env) (@prepend a args)))) | |
438 | | m) my args)) | |
439 | = (these-are my args) | |
440 | ||
441 | Macros can return macros. | |
442 | ||
443 | | (define literal (@macro (s a e) (@head a))) | |
444 | | (define bind (@macro (self args env) | |
445 | | (@eval | |
446 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
447 | | (@head (@tail (@tail args)))))) | |
448 | | (define let (@macro (self args env) | |
449 | | (bind bindings (@head args) | |
450 | | (@if (@equal? bindings ()) | |
451 | | (@eval env (@head (@tail args))) | |
452 | | (bind binding (@head bindings) | |
453 | | (bind name (@head binding) | |
454 | | (@if (@symbol? name) | |
455 | | (bind value (@eval env (@head (@tail binding))) | |
456 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
457 | | (bind newbindings (@tail bindings) | |
458 | | (bind newargs (@prepend newbindings (@tail args)) | |
459 | | (@eval newenv (@prepend self newargs)))))) | |
460 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
461 | | (display | |
462 | | (let | |
463 | | ((mk (@macro (self argsa env) | |
464 | | (@macro (self argsb env) | |
465 | | (@prepend (@head argsb) argsa)))) | |
466 | | (mk2 (mk vindaloo))) | |
467 | | (mk2 chicken))) | |
468 | = (chicken vindaloo) | |
469 | ||
470 | Arguments to macros shadow any other bindings in effect. | |
471 | ||
472 | | (define literal (@macro (s a e) (@head a))) | |
473 | | (define bind (@macro (self args env) | |
474 | | (@eval | |
475 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
476 | | (@head (@tail (@tail args)))))) | |
477 | | (define let (@macro (self args env) | |
478 | | (bind bindings (@head args) | |
479 | | (@if (@equal? bindings ()) | |
480 | | (@eval env (@head (@tail args))) | |
481 | | (bind binding (@head bindings) | |
482 | | (bind name (@head binding) | |
483 | | (@if (@symbol? name) | |
484 | | (bind value (@eval env (@head (@tail binding))) | |
485 | | (bind newenv (@prepend (@prepend name (@prepend value ())) env) | |
486 | | (bind newbindings (@tail bindings) | |
487 | | (bind newargs (@prepend newbindings (@tail args)) | |
488 | | (@eval newenv (@prepend self newargs)))))) | |
489 | | (@raise (prepend (literal illegal-binding) (@prepend binding ())))))))))) | |
490 | | (display | |
491 | | (let | |
492 | | ((args (literal a)) | |
493 | | (b (@macro (self args env) (@prepend args args)))) | |
494 | | (b 7))) | |
495 | = ((7) 7) | |
496 | ||
497 | `self` is there to let you write recursive macros. The following | |
498 | example demonstrates this; it evaluates `(prepend b d)` in an environment | |
499 | where all the identifiers you list after `qqq` have been bound to 0. | |
500 | ||
501 | | (define literal (@macro (s a e) (@head a))) | |
502 | | (define bind (@macro (self args env) | |
503 | | (@eval | |
504 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
505 | | (@head (@tail (@tail args)))))) | |
506 | | (display | |
507 | | (bind qqq | |
508 | | (@macro (self args env) | |
509 | | (@if (@equal? args ()) | |
510 | | (@eval env (literal (@prepend b (@prepend d ())))) | |
511 | | (@eval (@prepend (@prepend (@head args) (@prepend 0 ())) env) | |
512 | | (@prepend self (@tail args))))) | |
513 | | (bind b 1 (bind d 4 (qqq b c d))))) | |
514 | = (0 0) | |
515 | ||
516 | | (define literal (@macro (s a e) (@head a))) | |
517 | | (define bind (@macro (self args env) | |
518 | | (@eval | |
519 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
520 | | (@head (@tail (@tail args)))))) | |
521 | | (display | |
522 | | (bind qqq | |
523 | | (@macro (self args env) | |
524 | | (@if (@equal? args ()) | |
525 | | (@eval env (literal (@prepend b (@prepend d ())))) | |
526 | | (@eval (@prepend (@prepend (@head args) (@prepend 0 ())) env) | |
527 | | (@prepend self (@tail args))))) | |
528 | | (bind b 1 (bind d 4 (qqq x y z))))) | |
529 | = (1 4) | |
530 | ||
531 | Your recursive `macro` application doesn't have to be tail-recursive. | |
532 | ||
533 | | (define literal (@macro (s a e) (@head a))) | |
534 | | (define bind (@macro (self args env) | |
535 | | (@eval | |
536 | | (@prepend (@prepend (@head args) (@prepend (@eval env (@head (@tail args))) ())) env) | |
537 | | (@head (@tail (@tail args)))))) | |
538 | | (display | |
539 | | (bind make-env | |
540 | | (@macro (self args env) | |
541 | | (@if (@equal? args ()) | |
542 | | () | |
543 | | (@prepend (@prepend (@head args) | |
544 | | (@prepend (@eval env (@head args)) ())) | |
545 | | (@eval env | |
546 | | (@prepend self (@tail args)))))) | |
547 | | (bind b 1 (bind d 4 (make-env b d @macro))))) | |
548 | = ((b 1) (d 4) (@macro @macro)) | |
549 | ||
550 | `@macro` expects exactly two arguments. | |
551 | ||
552 | `@macro` expects its first argument to be a list of exactly three | |
553 | symbols. | |
554 | ||
555 | ### `@raise` ### | |
556 | ||
557 | `@raise` evaluates its argument to obtain a value, then raises an | |
558 | exception with that value. | |
559 | ||
560 | If no exception handlers have been installed in the execution | |
561 | history, the Robin program will terminate with an error, ceasing execution | |
562 | of all Robin processes immediately, returning control to the operating | |
563 | system. For the sake of usability, the error should include a message which | |
564 | refers to the exception that triggered it, but this is not a strict | |
565 | requirement. | |
566 | ||
567 | | (display | |
568 | | (@raise 999999)) | |
569 | ? uncaught exception: 999999 | |
570 | ||
571 | `@raise`'s single argument may be any kind of value, but `raise` expects | |
572 | exactly one argument. | |
573 | ||
574 | A Robin environment may install exception handlers which are not defined | |
575 | in Robin code itself. (i.e. exiting to the operating system is not a | |
576 | strict requirement.) | |
577 | ||
578 | ### `@catch` ### | |
579 | ||
580 | `@catch` installs an exception handler. | |
581 | ||
582 | If an exception is raised when evaluating the final argument of | |
583 | `@catch`, the exception value is bound to the symbol given as the | |
584 | first argument of `@catch`, and the second argument of `@catch` is | |
585 | evaluated in that new environment. | |
586 | ||
587 | | (define literal (@macro (s a e) (@head a))) | |
588 | | (define list (@macro (self args env) | |
589 | | (@if (@equal? args ()) | |
590 | | () | |
591 | | (@prepend (@eval env (@head args)) | |
592 | | (@eval env (@prepend self (@tail args))))))) | |
593 | | (display | |
594 | | (@catch error (list error #f) | |
595 | | (@raise (literal (nasty-value 999999))))) | |
596 | = ((nasty-value 999999) #f) | |
597 | ||
598 | `@catch` *cannot necessarily* catch exceptions raised by intrinsics. | |
599 | It ought to be able to catch exceptions raised by intrinsics wrappers, | |
600 | though. | |
601 | ||
602 | The innermost `@catch` will catch the exception. | |
603 | ||
604 | | (define literal (@macro (s a e) (@head a))) | |
605 | | (define list (@macro (self args env) | |
606 | | (@if (@equal? args ()) | |
607 | | () | |
608 | | (@prepend (@eval env (@head args)) | |
609 | | (@eval env (@prepend self (@tail args))))))) | |
610 | | (display | |
611 | | (@catch error (list error 5) | |
612 | | (@catch error (list error 9) | |
613 | | (@raise (literal derpy-value))))) | |
614 | = (derpy-value 9) | |
615 | ||
616 | An exception raised from within an exception handler is | |
617 | caught by the next innermost exception handler. | |
618 | ||
619 | | (define list (@macro (self args env) | |
620 | | (@if (@equal? args ()) | |
621 | | () | |
622 | | (@prepend (@eval env (@head args)) | |
623 | | (@eval env (@prepend self (@tail args))))))) | |
624 | | (display | |
625 | | (@catch error (list error 5) | |
626 | | (@catch error (list error 9) | |
627 | | (@catch error (@raise (list error error)) | |
628 | | (@raise 7))))) | |
629 | = ((7 7) 9) | |
630 | ||
631 | `@catch` expects its first argument to be an identifier. | |
632 | ||
633 | `@catch` expects exactly three arguments. | |
634 | ||
635 | TODO we should probably have some tests that prove that `@catch` can | |
636 | catch errors raised from inside macros. |
0 | Robin: Modules | |
1 | ============== | |
2 | ||
3 | In this document, "Robin" refers to the Robin programming language | |
4 | version 0.2. | |
5 | ||
6 | Robin's module system is this: Robin does not have a module system. | |
7 | ||
8 | We're still working this out, so bear with us. Let's start with | |
9 | some fundamental principles of Robin. You may love them or think | |
10 | they are stupid (I can't tell, myself,) but they are what they are. | |
11 | ||
12 | * The core Robin language includes only a handful of symbols, | |
13 | called _intrinsics_. These represent functionality that would | |
14 | be impossible or highly impractical to write in Robin itself. | |
15 | ||
16 | * A Robin program may, of course, define new symbols internal | |
17 | to that program, by assigning them meanings in its environment. | |
18 | ||
19 | * The Robin language expresses Robin programs; it does not | |
20 | express metadata about Robin programs. | |
21 | ||
22 | * Corollary: the contents of a Robin program is kept separate | |
23 | from the metadata about that Robin program. | |
24 | ||
25 | * Corollary: a Robin program that uses a symbol which is defined | |
26 | outside of that program does not, and in fact _cannot_, care | |
27 | where it is defined. | |
28 | ||
29 | * Corollary: dependencies between Robin (sub)programs and/or | |
30 | modules is an implementation-level concern, not a | |
31 | language-level concern. | |
32 | ||
33 | * Corollary: how the reference implementation solves the problem | |
34 | of dependencies between Robin programs is not necessarily how | |
35 | any other implementation should solve the problem. | |
36 | ||
37 | * ... all the Robin language really "knows" is that a Robin | |
38 | program may be split up into seperate "files" (where "file" means | |
39 | "input of program text into the implementation", I guess.) | |
40 | ||
41 | * Robin recognizes a set of symbols, currently called `stdlib`, | |
42 | that (should) have a (relatively) fixed meaning in all Robin | |
43 | programs, whether they are used in any given program or not. | |
44 | ||
45 | * Note (that should be elsewhere?): most of the macros defined | |
46 | in `stdlib` are supposed to, intentionally, take a fixed number | |
47 | of arguments for some reason (nominally, to make some kind of | |
48 | future static analysis easier.) | |
49 | ||
50 | * It is something like Maslow's hierarchy of needs. Robin's | |
51 | intrinsics make programming possible (*barely* possible — | |
52 | survival-level.) Robin's `stdlib` makes programming liveable. | |
53 | If there was another level, it might make programming pleasant, | |
54 | even. | |
55 | ||
56 | Some implications of this setup in practice are: | |
57 | ||
58 | * If you distribute a Robin program to someone else, you need to | |
59 | tell them (somehow) what other Robin (sub)programs/modules it | |
60 | depends on. | |
61 | ||
62 | * Actually this is hardly different from C, where dependency | |
63 | information is encoded both in `#include`'s and in a `Makefile` | |
64 | or similar, which links in the correct modules. The difference | |
65 | in Robin is simply that there are no `#include`s. | |
66 | ||
67 | * Other languages, such as Haskell and Python, try to include | |
68 | all dependency information in the program source code itself. | |
69 | This does away with `Makefile`-type dependency information, | |
70 | but at the cost of entangling programs and metadata about | |
71 | programs into the same files, into the same language grammar. | |
72 | ||
73 | * It would be entirely possible to define a "Robin dependency | |
74 | language" which: | |
75 | ||
76 | * describes the dependencies between different Robin programs | |
77 | * informs a tool like `make` | |
78 | * uses Robin's syntax | |
79 | * and perhaps even embeds Robin as an embedded language | |
80 | (and thus perhaps appears as a Robin "top-level form") | |
81 | ||
82 | ...*but*, the important thing to note is that such a language | |
83 | would *not be Robin itself*. | |
84 | ||
85 | * Any symbol in `stdlib` could be implemented in any language | |
86 | whatsoever, as long as the implementation knows what the | |
87 | semantics of the symbol is. | |
88 | ||
89 | The more pragmatic aspect of how the reference implementation | |
90 | currently handles the issue of dependencies between Robin programs, | |
91 | keeping in mind that this is an implementation issue and _not_ a | |
92 | language issue, and thus that the reference implementation is _not_ | |
93 | normative in this regard: | |
94 | ||
95 | * Each symbol defined in the Robin `stdlib` is written in its own | |
96 | Robin source file in the `stdlib` subdirectory, bundled along | |
97 | with tests for it. | |
98 | ||
99 | * All of the symbols in the `stdlib` directory are implemented in | |
100 | Robin. This is because, being a reference implementation, they | |
101 | are "executable specifications" rather than production code. | |
102 | They are supposed to be correct and simple and understandable, | |
103 | rather than performant. | |
104 | ||
105 | * Groups of symbols in the `stdlib` are collected into files | |
106 | called "packages", in the `pkg` subdirectory, which are simply | |
107 | concatenations, topologically sorted by dependency, of those | |
108 | individual files in the `stdlib` subdirectory. (These packages | |
109 | are built both by `./build.sh` and `./test.sh`.) | |
110 | ||
111 | * The groupings of symbols within a package follow certain themes, | |
112 | but are largely arbitrary, due to the ease with which a | |
113 | particular symbol could be grouped into two different packages | |
114 | by theme, and partly done for the convenience of the test suite, | |
115 | and to make dependencies work out "nicely", so that symbols can | |
116 | be implemented in terms of other symbols. | |
117 | ||
118 | * However, two packages have the following justifications: | |
119 | ||
120 | * The package `intrinsics-wrappers` contains macros which are | |
121 | simply wrappers for the intrinsics with the same names | |
122 | (prefixed with `@`). These serve two purposes: to let | |
123 | you not have to type `@` all the time, and to perform | |
124 | better argument type and number checking than the intrinsics | |
125 | are defined to do. | |
126 | ||
127 | * The package `small` is identified as a fairly minimal set | |
128 | of symbols to make programming tolerable | |
129 | (somewhere between possible and liveable in that "Maslow's | |
130 | hierarchy" analogy.) No symbol in it depends on any symbol | |
131 | defined in any other package; only intrinsics and other symbols | |
132 | in `small`. The price paid for this is that macros in | |
133 | `small`, like the intrinsics, do not have very good argument | |
134 | checking. | |
135 | ||
136 | Note that `intrinsics-wrappers` depends on `small`; the use | |
137 | `bind-args` to do the argument checking, which in turn needs | |
138 | `let` and `bind` and so forth. | |
139 | ||
140 | For a graphical depiction of the "hierarchy" of defined symbols | |
141 | (which is not really a proper hierarchy), please see | |
142 | `doc/Hierarchy_of_Defined_Symbols.html` (it's in HTML because it'd | |
143 | be trickier to depict in plain text or Markdown.) |
0 | Robin: Modules | |
1 | ============== | |
2 | ||
3 | In this document, "Robin" refers to the Robin programming language | |
4 | version 0.2. | |
5 | ||
6 | Robin's module system is this: Robin does not have a module system. | |
7 | ||
8 | We're still working this out, so bear with us. Let's start with | |
9 | some fundamental principles of Robin. You may love them or think | |
10 | they are stupid (I can't tell, myself,) but they are what they are. | |
11 | ||
12 | * The core Robin language includes only a handful of symbols, | |
13 | called _intrinsics_. These represent functionality that would | |
14 | be impossible or highly impractical to write in Robin itself. | |
15 | ||
16 | * A Robin program may, of course, define new symbols internal | |
17 | to that program, by assigning them meanings in its environment. | |
18 | ||
19 | * The Robin language expresses Robin programs; it does not | |
20 | express metadata about Robin programs. | |
21 | ||
22 | * Corollary: the contents of a Robin program is kept separate | |
23 | from the metadata about that Robin program. | |
24 | ||
25 | * Corollary: a Robin program that uses a symbol which is defined | |
26 | outside of that program does not, and in fact _cannot_, care | |
27 | where it is defined. | |
28 | ||
29 | * Corollary: dependencies between Robin (sub)programs and/or | |
30 | modules is an implementation-level concern, not a | |
31 | language-level concern. | |
32 | ||
33 | * Corollary: how the reference implementation solves the problem | |
34 | of dependencies between Robin programs is not necessarily how | |
35 | any other implementation should solve the problem. | |
36 | ||
37 | * ... all the Robin language really "knows" is that a Robin | |
38 | program may be split up into seperate "files" (where "file" means | |
39 | "input of program text into the implementation", I guess.) | |
40 | ||
41 | * Robin recognizes a set of symbols, currently called `stdlib`, | |
42 | that (should) have a (relatively) fixed meaning in all Robin | |
43 | programs, whether they are used in any given program or not. | |
44 | ||
45 | * Note (that should be elsewhere?): most of the macros defined | |
46 | in `stdlib` are supposed to, intentionally, take a fixed number | |
47 | of arguments for some reason (nominally, to make some kind of | |
48 | future static analysis easier.) | |
49 | ||
50 | * It is something like Maslow's hierarchy of needs. Robin's | |
51 | intrinsics make programming possible (*barely* possible — | |
52 | survival-level.) Robin's `stdlib` makes programming liveable. | |
53 | If there was another level, it might make programming pleasant, | |
54 | even. | |
55 | ||
56 | Some implications of this setup in practice are: | |
57 | ||
58 | * If you distribute a Robin program to someone else, you need to | |
59 | tell them (somehow) what other Robin (sub)programs/modules it | |
60 | depends on. | |
61 | ||
62 | * Actually this is hardly different from C, where dependency | |
63 | information is encoded both in `#include`'s and in a `Makefile` | |
64 | or similar, which links in the correct modules. The difference | |
65 | in Robin is simply that there are no `#include`s. | |
66 | ||
67 | * Other languages, such as Haskell and Python, try to include | |
68 | all dependency information in the program source code itself. | |
69 | This does away with `Makefile`-type dependency information, | |
70 | but at the cost of entangling programs and metadata about | |
71 | programs into the same files, into the same language grammar. | |
72 | ||
73 | * It would be entirely possible to define a "Robin dependency | |
74 | language" which: | |
75 | ||
76 | * describes the dependencies between different Robin programs | |
77 | * informs a tool like `make` | |
78 | * uses Robin's syntax | |
79 | * and perhaps even embeds Robin as an embedded language | |
80 | (and thus perhaps appears as a Robin "top-level form") | |
81 | ||
82 | ...*but*, the important thing to note is that such a language | |
83 | would *not be Robin itself*. | |
84 | ||
85 | * Any symbol in `stdlib` could be implemented in any language | |
86 | whatsoever, as long as the implementation knows what the | |
87 | semantics of the symbol is. | |
88 | ||
89 | The more pragmatic aspect of how the reference implementation | |
90 | currently handles the issue of dependencies between Robin programs, | |
91 | keeping in mind that this is an implementation issue and _not_ a | |
92 | language issue, and thus that the reference implementation is _not_ | |
93 | normative in this regard: | |
94 | ||
95 | * Each symbol defined in the Robin `stdlib` is written in its own | |
96 | Robin source file in the `stdlib` subdirectory, bundled along | |
97 | with tests for it. | |
98 | ||
99 | * All of the symbols in the `stdlib` directory are implemented in | |
100 | Robin. This is because, being a reference implementation, they | |
101 | are "executable specifications" rather than production code. | |
102 | They are supposed to be correct and simple and understandable, | |
103 | rather than performant. | |
104 | ||
105 | * Groups of symbols in the `stdlib` are collected into files | |
106 | called "packages", in the `pkg` subdirectory, which are simply | |
107 | concatenations, topologically sorted by dependency, of those | |
108 | individual files in the `stdlib` subdirectory. (These packages | |
109 | are built both by `./build.sh` and `./test.sh`.) | |
110 | ||
111 | * The groupings of symbols within a package follow certain themes, | |
112 | but are largely arbitrary, due to the ease with which a | |
113 | particular symbol could be grouped into two different packages | |
114 | by theme, and partly done for the convenience of the test suite, | |
115 | and to make dependencies work out "nicely", so that symbols can | |
116 | be implemented in terms of other symbols. | |
117 | ||
118 | * However, two packages have the following justifications: | |
119 | ||
120 | * The package `intrinsics-wrappers` contains macros which are | |
121 | simply wrappers for the intrinsics with the same names | |
122 | (prefixed with `@`). These serve two purposes: to let | |
123 | you not have to type `@` all the time, and to perform | |
124 | better argument type and number checking than the intrinsics | |
125 | are defined to do. | |
126 | ||
127 | * The package `small` is identified as a fairly minimal set | |
128 | of symbols to make programming tolerable | |
129 | (somewhere between possible and liveable in that "Maslow's | |
130 | hierarchy" analogy.) No symbol in it depends on any symbol | |
131 | defined in any other package; only intrinsics and other symbols | |
132 | in `small`. The price paid for this is that macros in | |
133 | `small`, like the intrinsics, do not have very good argument | |
134 | checking. | |
135 | ||
136 | Note that `intrinsics-wrappers` depends on `small`; the use | |
137 | `bind-args` to do the argument checking, which in turn needs | |
138 | `let` and `bind` and so forth. | |
139 | ||
140 | For a graphical depiction of the "hierarchy" of defined symbols | |
141 | (which is not really a proper hierarchy), please see | |
142 | `doc/Hierarchy_of_Defined_Symbols.html` (it's in HTML because it'd | |
143 | be trickier to depict in plain text or Markdown.) |
0 | Robin Reactors | |
1 | ============== | |
2 | ||
3 | To separate the concerns of computation and interaction, Robin provides | |
4 | a construct called a _reactor_. While normal S-expression evaluation | |
5 | accomplishes side-effect-free computation, reactors permit the construction | |
6 | of interactive programs. Reactors are similar to event handlers in | |
7 | languages such as Javascript, or to `gen_server`s in Erlang. | |
8 | ||
9 | In Robin, a reactor is installed by a top-level form with the syntax | |
10 | `(reactor LIST-OF-SYMBOLS STATE-EXPR BODY-EXPR)`. | |
11 | ||
12 | The first argument of the `reactor` form is a literal (unevaluated) list | |
13 | of symbols, called the _subscriptions_ for the reactor. Each symbol names | |
14 | a _facility_ with which the reactor wishes to be able to interact. | |
15 | ||
16 | The second argument is evaluated, and becomes the _initial state_ of the | |
17 | reactor. | |
18 | ||
19 | The third argument is evaluated, presumably to a macro. This is called the | |
20 | _body_ of the reactor. | |
21 | ||
22 | Whenever an event of interest to the reactor (as determined by the facilities | |
23 | with which the reactor requested interaction) occurs, the body is evaluated, | |
24 | being passed three (pre-evaluated) arguments: | |
25 | ||
26 | * A literal symbol called the _event code_, specifying what kind of event | |
27 | happened; | |
28 | * An arbitrary value called the _event payload_ containing more data about | |
29 | the event, in a format specific to that kind of event; and | |
30 | * The previous state of the reactor. (This will be the initial state | |
31 | if the reactor body has never before been evaluated.) | |
32 | ||
33 | Given these things, the body is expected to evaluate to a list where the | |
34 | first element is the new state of the reactor, and each of the subsequent | |
35 | elements is a _response_ to the facility. A response is itself a | |
36 | two-element list containing: | |
37 | ||
38 | * A literal symbol called the _response code_ specifying the kind of | |
39 | response to the event that is being made; and | |
40 | * An arbitrary value called the _response payload_ containing more data | |
41 | about the response, in a format specific to that kind of response. | |
42 | ||
43 | There may of course be zero responses in the returned list. If the | |
44 | returned value is not a list containing at least one element, no responses | |
45 | will be made and the state of the reactor will remain unchanged. | |
46 | ||
47 | It will be difficult to provide examples of how to use reactors without | |
48 | introducing a concrete facility to react with, so we'll do that now. | |
49 | ||
50 | Facility `line-terminal` | |
51 | ------------------------ | |
52 | ||
53 | -> Tests for functionality "Interpret Robin Program (with Small)" | |
54 | ||
55 | The `line-terminal` facility allows a Robin program to interact with a | |
56 | terminal-based, line-buffered "standard I/O" a la Unix. Note that there is | |
57 | nothing in the Robin language that requires this to be "the real standard | |
58 | I/O"; Robin denies any knowledge of that sort of thing. It could well be | |
59 | simulated with modal dialogue boxes in a GUI, or with textareas on a web | |
60 | page under Javascript. | |
61 | ||
62 | A reactor accessing the `line-terminal` facility may make responses in the | |
63 | form | |
64 | ||
65 | (writeln <STRING> <NEW-STATE>) | |
66 | ||
67 | The `<STRING>` argument should be a Robin string (list of integers). Those | |
68 | integers, as bytes, are sent to something that resembles the "standard output" | |
69 | under Unix. (When attached to a terminal, this would typically cause an | |
70 | ASCII representation of those bytes to be displayed.) | |
71 | ||
72 | In the following example, the string is printed multiple times because the | |
73 | reactor is reacting indiscriminately to multiple events — we'll get to that | |
74 | in a second. In addition, note that this reactor essentially doesn't keep | |
75 | any state — the initial state of the reactor is simply the integer 0, and | |
76 | the state is set to 0 after each event is reacted to. | |
77 | ||
78 | | (reactor (line-terminal) 0 (macro (self args env) | |
79 | | (list 0 (list (literal writeln) (literal ''Hello, world!''))))) | |
80 | = Hello, world! | |
81 | = Hello, world! | |
82 | ||
83 | Reactors which interact with `line-terminal` receive three kinds of events. | |
84 | ||
85 | The first, `init`, is sent when the facility with which the reactor is | |
86 | reacting initially becomes ready for use. In the example above, this is one | |
87 | of the events actually being reacted to (even though we don't explicitly | |
88 | check for it.) To make it explicit, and correct, | |
89 | ||
90 | | (reactor (line-terminal) 0 (macro (self args env) | |
91 | | (bind event (head args) | |
92 | | (if (equal? event (literal init)) | |
93 | | (list 0 (list (literal writeln) (literal ''Hello, world!''))) | |
94 | | (list 0))))) | |
95 | = Hello, world! | |
96 | ||
97 | The payload for `init` is not yet defined. | |
98 | ||
99 | Note that the arguments to the reactor body come already-evaluated, so | |
100 | there's no need to write it as a `fun` or to use `bind-args`. | |
101 | ||
102 | The second event, `readln`, is sent when a line of text is received | |
103 | on the "standard input". The payload for this event is a Robin string | |
104 | of the line of text received. This string does not contain any end-of-line | |
105 | marker characters. | |
106 | ||
107 | Thus we can construct a simple `cat` program: | |
108 | ||
109 | | (reactor (line-terminal) 0 (macro (self args env) | |
110 | | (bind event (head args) | |
111 | | (bind payload (head (tail args)) | |
112 | | (if (equal? event (literal readln)) | |
113 | | (list 0 (list (literal writeln) payload)) | |
114 | | (list 0)))))) | |
115 | + Cat | |
116 | + Dog | |
117 | = Cat | |
118 | = Dog | |
119 | ||
120 | The third event, `eof`, is sent when no more input is available | |
121 | ("end of file") on the "standard input". Perhaps input was redirected | |
122 | from a file and that file has come to an end, or perhaps the user pressed | |
123 | Ctrl+D. | |
124 | ||
125 | The payload for `eof` is not yet defined. | |
126 | ||
127 | Here is an example of handling all three kinds of events: | |
128 | ||
129 | | (reactor (line-terminal) 0 (macro (self args env) | |
130 | | (bind event (head args) | |
131 | | (bind payload (head (tail args)) | |
132 | | (choose | |
133 | | ((equal? event (literal init)) | |
134 | | (list 0 (list (literal writeln) (literal ''Hello, world!'')))) | |
135 | | ((equal? event (literal readln)) | |
136 | | (list 0 (list (literal writeln) payload))) | |
137 | | ((equal? event (literal eof)) | |
138 | | (list 0 (list (literal writeln) (literal ''Goodbye, world!'')))) | |
139 | | (else | |
140 | | (list 0))))))) | |
141 | + Cat | |
142 | + Dog | |
143 | = Hello, world! | |
144 | = Cat | |
145 | = Dog | |
146 | = Goodbye, world! | |
147 | ||
148 | A reactor accessing the `line-terminal` facility may also make responses in the | |
149 | form | |
150 | ||
151 | (close <ARG> <NEW-STATE>) | |
152 | ||
153 | This informs the `line-terminal` facility that it is no longer needed by this | |
154 | reactor, and can stop sending it events. The `<ARG>` is not yet defined. | |
155 | ||
156 | So the "Hello, world" example above still isn't quite right; the only reason | |
157 | it looks right is because the test suite is giving it an empty input, so it | |
158 | gets an EOF and terminates. If you run it on the command line, the Robin | |
159 | implementation will wait for more input after printing "Hello, world!". | |
160 | (As is its wont. It's not expected to know that none of its reactors wants | |
161 | or needs more input.) | |
162 | ||
163 | To write it properly, in classic hello-world form, we'd have to say | |
164 | ||
165 | | (reactor (line-terminal) 0 (macro (self args env) | |
166 | | (bind event (head args) | |
167 | | (if (equal? event (literal init)) | |
168 | | (list 0 | |
169 | | (list (literal writeln) (literal ''Hello, world!'')) | |
170 | | (list (literal close) 0)) | |
171 | | (list 0))))) | |
172 | = Hello, world! | |
173 | ||
174 | General Reactor properties | |
175 | -------------------------- | |
176 | ||
177 | Facilities can handle multiple responses in response to an event. | |
178 | ||
179 | | (reactor (line-terminal) 0 (macro (self args env) | |
180 | | (bind event (head args) | |
181 | | (bind payload (head (tail args)) | |
182 | | (if (equal? event (literal readln)) | |
183 | | (list 0 | |
184 | | (list (literal writeln) (literal ''Line:'')) | |
185 | | (list (literal writeln) payload)) | |
186 | | (list 0)))))) | |
187 | + Cat | |
188 | + Dog | |
189 | = Line: | |
190 | = Cat | |
191 | = Line: | |
192 | = Dog | |
193 | ||
194 | When receiving a malformed response, a facility may produce a warning | |
195 | message of some kind, but it should otherwise ignore it and keep going. | |
196 | ||
197 | | (reactor (line-terminal) 0 (macro (self args env) | |
198 | | (bind event (head args) | |
199 | | (bind payload (head (tail args)) | |
200 | | (if (equal? event (literal readln)) | |
201 | | (list 0 | |
202 | | (literal what-is-this) | |
203 | | (literal i-dont-even) | |
204 | | (list (literal writeln) payload)) | |
205 | | (list 0)))))) | |
206 | + Cat | |
207 | + Dog | |
208 | = Cat | |
209 | = Dog | |
210 | ||
211 | After a reactor closes, additional responses are ignored. (Thus, a close | |
212 | response, if sent, should be last in the list.) | |
213 | ||
214 | | (reactor (line-terminal) 0 (macro (self args env) | |
215 | | (bind event (head args) | |
216 | | (bind payload (head (tail args)) | |
217 | | (if (equal? event (literal init)) | |
218 | | (list 0 | |
219 | | (list (literal writeln) (literal ''Hello'')) | |
220 | | (list (literal close) 0) | |
221 | | (list (literal writeln) (literal ''there''))) | |
222 | | (list 0)))))) | |
223 | = Hello | |
224 | ||
225 | Reactors can keep state. | |
226 | ||
227 | | (define inc (macro (self args env) | |
228 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
229 | | (reactor (line-terminal) 65 (macro (self args env) | |
230 | | (bind event (head args) | |
231 | | (bind payload (head (tail args)) | |
232 | | (bind state (head (tail (tail args))) | |
233 | | (if (equal? event (literal readln)) | |
234 | | (list (inc state) (list (literal writeln) (list state))) | |
235 | | (list state))))))) | |
236 | + Cat | |
237 | + Dog | |
238 | + Giraffe | |
239 | = A | |
240 | = B | |
241 | = C | |
242 | ||
243 | Multiple reactors can be installed for a facility. | |
244 | ||
245 | | (define inc (macro (self args env) | |
246 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
247 | | (reactor (line-terminal) 65 (macro (self args env) | |
248 | | (bind event (head args) | |
249 | | (bind payload (head (tail args)) | |
250 | | (bind state (head (tail (tail args))) | |
251 | | (if (equal? event (literal readln)) | |
252 | | (list (inc state) (list (literal writeln) (list state))) | |
253 | | (list state))))))) | |
254 | | (reactor (line-terminal) 0 (macro (self args env) | |
255 | | (bind event (head args) | |
256 | | (bind payload (head (tail args)) | |
257 | | (if (equal? event (literal readln)) | |
258 | | (list 0 (list (literal writeln) payload)) | |
259 | | (list 0)))))) | |
260 | + Cat | |
261 | + Dog | |
262 | + Giraffe | |
263 | = Cat | |
264 | = A | |
265 | = Dog | |
266 | = B | |
267 | = Giraffe | |
268 | = C | |
269 | ||
270 | Reactors react in the *opposite* order they were installed. | |
271 | ||
272 | | (define inc (macro (self args env) | |
273 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
274 | | (reactor (line-terminal) 0 (macro (self args env) | |
275 | | (bind event (head args) | |
276 | | (bind payload (head (tail args)) | |
277 | | (if (equal? event (literal readln)) | |
278 | | (list 0 (list (literal writeln) payload)) | |
279 | | (list 0)))))) | |
280 | | (reactor (line-terminal) 65 (macro (self args env) | |
281 | | (bind event (head args) | |
282 | | (bind payload (head (tail args)) | |
283 | | (bind state (head (tail (tail args))) | |
284 | | (if (equal? event (literal readln)) | |
285 | | (list (inc state) (list (literal writeln) (list state))) | |
286 | | (list state))))))) | |
287 | + Cat | |
288 | + Dog | |
289 | + Giraffe | |
290 | = A | |
291 | = Cat | |
292 | = B | |
293 | = Dog | |
294 | = C | |
295 | = Giraffe | |
296 | ||
297 | Closing one reactor does not stop others. | |
298 | ||
299 | | (define inc (macro (self args env) | |
300 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
301 | | (reactor (line-terminal) 0 (macro (self args env) | |
302 | | (bind event (head args) | |
303 | | (bind payload (head (tail args)) | |
304 | | (if (equal? event (literal readln)) | |
305 | | (list 0 (list (literal writeln) payload)) | |
306 | | (list 0)))))) | |
307 | | (reactor (line-terminal) 65 (macro (self args env) | |
308 | | (bind event (head args) | |
309 | | (bind payload (head (tail args)) | |
310 | | (bind state (head (tail (tail args))) | |
311 | | (if (equal? state 67) | |
312 | | (list state (list (literal close) 0)) | |
313 | | (if (equal? event (literal readln)) | |
314 | | (list (inc state) (list (literal writeln) (list state))) | |
315 | | (list state)))))))) | |
316 | + Cat | |
317 | + Dog | |
318 | + Giraffe | |
319 | + Turkey | |
320 | + Wallaby | |
321 | = A | |
322 | = Cat | |
323 | = B | |
324 | = Dog | |
325 | = Giraffe | |
326 | = Turkey | |
327 | = Wallaby | |
328 | ||
329 | In fact the `init` event type and the `close` response are not specific | |
330 | to `line-terminal`, but rather, generic; every facility that a reactor can | |
331 | react to should send and understand them. | |
332 | ||
333 | This leaves some open questions about reactors (and so their semantics | |
334 | will definitely change slightly in a subsequent 0.x version of Robin.) | |
335 | Namely: | |
336 | ||
337 | * How does the reactor know which facility the `init` is for? | |
338 | (Probably it should be named as part of the payload of `init`?) | |
339 | * Can a reactor respond with a `close` to a facility other than the | |
340 | facility that sent it the event it is currently handling? | |
341 | * Currently reactors cannot communicate with each other at all. | |
342 | How can reactors communicate with each other? (Our idea is to have | |
343 | a "reactor bus" facility which can relay responses from one reactor | |
344 | into an event for another reactor.) |
0 | Robin Reactors | |
1 | ============== | |
2 | ||
3 | To separate the concerns of computation and interaction, Robin provides | |
4 | a construct called a _reactor_. While normal S-expression evaluation | |
5 | accomplishes side-effect-free computation, reactors permit the construction | |
6 | of interactive programs. Reactors are similar to event handlers in | |
7 | languages such as Javascript, or to `gen_server`s in Erlang. | |
8 | ||
9 | In Robin, a reactor is installed by a top-level form with the syntax | |
10 | `(reactor LIST-OF-SYMBOLS STATE-EXPR BODY-EXPR)`. | |
11 | ||
12 | The first argument of the `reactor` form is a literal (unevaluated) list | |
13 | of symbols, called the _subscriptions_ for the reactor. Each symbol names | |
14 | a _facility_ with which the reactor wishes to be able to interact. | |
15 | ||
16 | The second argument is evaluated, and becomes the _initial state_ of the | |
17 | reactor. | |
18 | ||
19 | The third argument is evaluated, presumably to a macro. This is called the | |
20 | _body_ of the reactor. | |
21 | ||
22 | Whenever an event of interest to the reactor (as determined by the facilities | |
23 | with which the reactor requested interaction) occurs, the body is evaluated, | |
24 | being passed three (pre-evaluated) arguments: | |
25 | ||
26 | * A literal symbol called the _event code_, specifying what kind of event | |
27 | happened; | |
28 | * An arbitrary value called the _event payload_ containing more data about | |
29 | the event, in a format specific to that kind of event; and | |
30 | * The previous state of the reactor. (This will be the initial state | |
31 | if the reactor body has never before been evaluated.) | |
32 | ||
33 | Given these things, the body is expected to evaluate to a list where the | |
34 | first element is the new state of the reactor, and each of the subsequent | |
35 | elements is a _response_ to the facility. A response is itself a | |
36 | two-element list containing: | |
37 | ||
38 | * A literal symbol called the _response code_ specifying the kind of | |
39 | response to the event that is being made; and | |
40 | * An arbitrary value called the _response payload_ containing more data | |
41 | about the response, in a format specific to that kind of response. | |
42 | ||
43 | There may of course be zero responses in the returned list. If the | |
44 | returned value is not a list containing at least one element, no responses | |
45 | will be made and the state of the reactor will remain unchanged. | |
46 | ||
47 | It will be difficult to provide examples of how to use reactors without | |
48 | introducing a concrete facility to react with, so we'll do that now. | |
49 | ||
50 | Facility `line-terminal` | |
51 | ------------------------ | |
52 | ||
53 | -> Tests for functionality "Interpret Robin Program (with Small)" | |
54 | ||
55 | The `line-terminal` facility allows a Robin program to interact with a | |
56 | terminal-based, line-buffered "standard I/O" a la Unix. Note that there is | |
57 | nothing in the Robin language that requires this to be "the real standard | |
58 | I/O"; Robin denies any knowledge of that sort of thing. It could well be | |
59 | simulated with modal dialogue boxes in a GUI, or with textareas on a web | |
60 | page under Javascript. | |
61 | ||
62 | A reactor accessing the `line-terminal` facility may make responses in the | |
63 | form | |
64 | ||
65 | (writeln <STRING> <NEW-STATE>) | |
66 | ||
67 | The `<STRING>` argument should be a Robin string (list of integers). Those | |
68 | integers, as bytes, are sent to something that resembles the "standard output" | |
69 | under Unix. (When attached to a terminal, this would typically cause an | |
70 | ASCII representation of those bytes to be displayed.) | |
71 | ||
72 | In the following example, the string is printed multiple times because the | |
73 | reactor is reacting indiscriminately to multiple events — we'll get to that | |
74 | in a second. In addition, note that this reactor essentially doesn't keep | |
75 | any state — the initial state of the reactor is simply the integer 0, and | |
76 | the state is set to 0 after each event is reacted to. | |
77 | ||
78 | | (reactor (line-terminal) 0 (macro (self args env) | |
79 | | (list 0 (list (literal writeln) (literal ''Hello, world!''))))) | |
80 | = Hello, world! | |
81 | = Hello, world! | |
82 | ||
83 | Reactors which interact with `line-terminal` receive three kinds of events. | |
84 | ||
85 | The first, `init`, is sent when the facility with which the reactor is | |
86 | reacting initially becomes ready for use. In the example above, this is one | |
87 | of the events actually being reacted to (even though we don't explicitly | |
88 | check for it.) To make it explicit, and correct, | |
89 | ||
90 | | (reactor (line-terminal) 0 (macro (self args env) | |
91 | | (bind event (head args) | |
92 | | (if (equal? event (literal init)) | |
93 | | (list 0 (list (literal writeln) (literal ''Hello, world!''))) | |
94 | | (list 0))))) | |
95 | = Hello, world! | |
96 | ||
97 | The payload for `init` is not yet defined. | |
98 | ||
99 | Note that the arguments to the reactor body come already-evaluated, so | |
100 | there's no need to write it as a `fun` or to use `bind-args`. | |
101 | ||
102 | The second event, `readln`, is sent when a line of text is received | |
103 | on the "standard input". The payload for this event is a Robin string | |
104 | of the line of text received. This string does not contain any end-of-line | |
105 | marker characters. | |
106 | ||
107 | Thus we can construct a simple `cat` program: | |
108 | ||
109 | | (reactor (line-terminal) 0 (macro (self args env) | |
110 | | (bind event (head args) | |
111 | | (bind payload (head (tail args)) | |
112 | | (if (equal? event (literal readln)) | |
113 | | (list 0 (list (literal writeln) payload)) | |
114 | | (list 0)))))) | |
115 | + Cat | |
116 | + Dog | |
117 | = Cat | |
118 | = Dog | |
119 | ||
120 | The third event, `eof`, is sent when no more input is available | |
121 | ("end of file") on the "standard input". Perhaps input was redirected | |
122 | from a file and that file has come to an end, or perhaps the user pressed | |
123 | Ctrl+D. | |
124 | ||
125 | The payload for `eof` is not yet defined. | |
126 | ||
127 | Here is an example of handling all three kinds of events: | |
128 | ||
129 | | (reactor (line-terminal) 0 (macro (self args env) | |
130 | | (bind event (head args) | |
131 | | (bind payload (head (tail args)) | |
132 | | (choose | |
133 | | ((equal? event (literal init)) | |
134 | | (list 0 (list (literal writeln) (literal ''Hello, world!'')))) | |
135 | | ((equal? event (literal readln)) | |
136 | | (list 0 (list (literal writeln) payload))) | |
137 | | ((equal? event (literal eof)) | |
138 | | (list 0 (list (literal writeln) (literal ''Goodbye, world!'')))) | |
139 | | (else | |
140 | | (list 0))))))) | |
141 | + Cat | |
142 | + Dog | |
143 | = Hello, world! | |
144 | = Cat | |
145 | = Dog | |
146 | = Goodbye, world! | |
147 | ||
148 | A reactor accessing the `line-terminal` facility may also make responses in the | |
149 | form | |
150 | ||
151 | (close <ARG> <NEW-STATE>) | |
152 | ||
153 | This informs the `line-terminal` facility that it is no longer needed by this | |
154 | reactor, and can stop sending it events. The `<ARG>` is not yet defined. | |
155 | ||
156 | So the "Hello, world" example above still isn't quite right; the only reason | |
157 | it looks right is because the test suite is giving it an empty input, so it | |
158 | gets an EOF and terminates. If you run it on the command line, the Robin | |
159 | implementation will wait for more input after printing "Hello, world!". | |
160 | (As is its wont. It's not expected to know that none of its reactors wants | |
161 | or needs more input.) | |
162 | ||
163 | To write it properly, in classic hello-world form, we'd have to say | |
164 | ||
165 | | (reactor (line-terminal) 0 (macro (self args env) | |
166 | | (bind event (head args) | |
167 | | (if (equal? event (literal init)) | |
168 | | (list 0 | |
169 | | (list (literal writeln) (literal ''Hello, world!'')) | |
170 | | (list (literal close) 0)) | |
171 | | (list 0))))) | |
172 | = Hello, world! | |
173 | ||
174 | General Reactor properties | |
175 | -------------------------- | |
176 | ||
177 | Facilities can handle multiple responses in response to an event. | |
178 | ||
179 | | (reactor (line-terminal) 0 (macro (self args env) | |
180 | | (bind event (head args) | |
181 | | (bind payload (head (tail args)) | |
182 | | (if (equal? event (literal readln)) | |
183 | | (list 0 | |
184 | | (list (literal writeln) (literal ''Line:'')) | |
185 | | (list (literal writeln) payload)) | |
186 | | (list 0)))))) | |
187 | + Cat | |
188 | + Dog | |
189 | = Line: | |
190 | = Cat | |
191 | = Line: | |
192 | = Dog | |
193 | ||
194 | When receiving a malformed response, a facility may produce a warning | |
195 | message of some kind, but it should otherwise ignore it and keep going. | |
196 | ||
197 | | (reactor (line-terminal) 0 (macro (self args env) | |
198 | | (bind event (head args) | |
199 | | (bind payload (head (tail args)) | |
200 | | (if (equal? event (literal readln)) | |
201 | | (list 0 | |
202 | | (literal what-is-this) | |
203 | | (literal i-dont-even) | |
204 | | (list (literal writeln) payload)) | |
205 | | (list 0)))))) | |
206 | + Cat | |
207 | + Dog | |
208 | = Cat | |
209 | = Dog | |
210 | ||
211 | After a reactor closes, additional responses are ignored. (Thus, a close | |
212 | response, if sent, should be last in the list.) | |
213 | ||
214 | | (reactor (line-terminal) 0 (macro (self args env) | |
215 | | (bind event (head args) | |
216 | | (bind payload (head (tail args)) | |
217 | | (if (equal? event (literal init)) | |
218 | | (list 0 | |
219 | | (list (literal writeln) (literal ''Hello'')) | |
220 | | (list (literal close) 0) | |
221 | | (list (literal writeln) (literal ''there''))) | |
222 | | (list 0)))))) | |
223 | = Hello | |
224 | ||
225 | Reactors can keep state. | |
226 | ||
227 | | (define inc (macro (self args env) | |
228 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
229 | | (reactor (line-terminal) 65 (macro (self args env) | |
230 | | (bind event (head args) | |
231 | | (bind payload (head (tail args)) | |
232 | | (bind state (head (tail (tail args))) | |
233 | | (if (equal? event (literal readln)) | |
234 | | (list (inc state) (list (literal writeln) (list state))) | |
235 | | (list state))))))) | |
236 | + Cat | |
237 | + Dog | |
238 | + Giraffe | |
239 | = A | |
240 | = B | |
241 | = C | |
242 | ||
243 | Multiple reactors can be installed for a facility. | |
244 | ||
245 | | (define inc (macro (self args env) | |
246 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
247 | | (reactor (line-terminal) 65 (macro (self args env) | |
248 | | (bind event (head args) | |
249 | | (bind payload (head (tail args)) | |
250 | | (bind state (head (tail (tail args))) | |
251 | | (if (equal? event (literal readln)) | |
252 | | (list (inc state) (list (literal writeln) (list state))) | |
253 | | (list state))))))) | |
254 | | (reactor (line-terminal) 0 (macro (self args env) | |
255 | | (bind event (head args) | |
256 | | (bind payload (head (tail args)) | |
257 | | (if (equal? event (literal readln)) | |
258 | | (list 0 (list (literal writeln) payload)) | |
259 | | (list 0)))))) | |
260 | + Cat | |
261 | + Dog | |
262 | + Giraffe | |
263 | = Cat | |
264 | = A | |
265 | = Dog | |
266 | = B | |
267 | = Giraffe | |
268 | = C | |
269 | ||
270 | Reactors react in the *opposite* order they were installed. | |
271 | ||
272 | | (define inc (macro (self args env) | |
273 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
274 | | (reactor (line-terminal) 0 (macro (self args env) | |
275 | | (bind event (head args) | |
276 | | (bind payload (head (tail args)) | |
277 | | (if (equal? event (literal readln)) | |
278 | | (list 0 (list (literal writeln) payload)) | |
279 | | (list 0)))))) | |
280 | | (reactor (line-terminal) 65 (macro (self args env) | |
281 | | (bind event (head args) | |
282 | | (bind payload (head (tail args)) | |
283 | | (bind state (head (tail (tail args))) | |
284 | | (if (equal? event (literal readln)) | |
285 | | (list (inc state) (list (literal writeln) (list state))) | |
286 | | (list state))))))) | |
287 | + Cat | |
288 | + Dog | |
289 | + Giraffe | |
290 | = A | |
291 | = Cat | |
292 | = B | |
293 | = Dog | |
294 | = C | |
295 | = Giraffe | |
296 | ||
297 | Closing one reactor does not stop others. | |
298 | ||
299 | | (define inc (macro (self args env) | |
300 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
301 | | (reactor (line-terminal) 0 (macro (self args env) | |
302 | | (bind event (head args) | |
303 | | (bind payload (head (tail args)) | |
304 | | (if (equal? event (literal readln)) | |
305 | | (list 0 (list (literal writeln) payload)) | |
306 | | (list 0)))))) | |
307 | | (reactor (line-terminal) 65 (macro (self args env) | |
308 | | (bind event (head args) | |
309 | | (bind payload (head (tail args)) | |
310 | | (bind state (head (tail (tail args))) | |
311 | | (if (equal? state 67) | |
312 | | (list state (list (literal close) 0)) | |
313 | | (if (equal? event (literal readln)) | |
314 | | (list (inc state) (list (literal writeln) (list state))) | |
315 | | (list state)))))))) | |
316 | + Cat | |
317 | + Dog | |
318 | + Giraffe | |
319 | + Turkey | |
320 | + Wallaby | |
321 | = A | |
322 | = Cat | |
323 | = B | |
324 | = Dog | |
325 | = Giraffe | |
326 | = Turkey | |
327 | = Wallaby | |
328 | ||
329 | In fact the `init` event type and the `close` response are not specific | |
330 | to `line-terminal`, but rather, generic; every facility that a reactor can | |
331 | react to should send and understand them. | |
332 | ||
333 | This leaves some open questions about reactors (and so their semantics | |
334 | will definitely change slightly in a subsequent 0.x version of Robin.) | |
335 | Namely: | |
336 | ||
337 | * How does the reactor know which facility the `init` is for? | |
338 | (Probably it should be named as part of the payload of `init`?) | |
339 | * Can a reactor respond with a `close` to a facility other than the | |
340 | facility that sent it the event it is currently handling? | |
341 | * Currently reactors cannot communicate with each other at all. | |
342 | How can reactors communicate with each other? (Our idea is to have | |
343 | a "reactor bus" facility which can relay responses from one reactor | |
344 | into an event for another reactor.) |
0 | Robin | |
1 | ===== | |
2 | ||
3 | This document defines the fundamental semantics of Robin (except for the | |
4 | meanings of the intrinsics: see `Intrinsics.markdown` for those.) | |
5 | ||
6 | -> Tests for functionality "Interpret core Robin Program" | |
7 | ||
8 | Top-level S-expressions | |
9 | ----------------------- | |
10 | ||
11 | A Robin program consists of a series of "top-level" S-expressions. | |
12 | Each top-level S-expression must have a particular form, but most of these | |
13 | top-level S-expressions may contain general, evaluatable S-expressions | |
14 | themselves. Allowable top-level forms are given in the subsections below. | |
15 | ||
16 | ### `display` ### | |
17 | ||
18 | `(display EXPR)` evaluates the EXPR and displays the result in a canonical | |
19 | S-expression rendering, followed by a newline. | |
20 | ||
21 | | (display #t) | |
22 | = #t | |
23 | ||
24 | Note that a Robin program may be split over several files in the filesystem. | |
25 | Also, more than one top-level S-expression may appear in a single file. | |
26 | ||
27 | | (display #t) | |
28 | | (display #f) | |
29 | = #t | |
30 | = #f | |
31 | ||
32 | ### `define` ### | |
33 | ||
34 | `(define ATOM EXPR)` defines a global name. | |
35 | ||
36 | | (define true #t) | |
37 | | (display true) | |
38 | = #t | |
39 | ||
40 | You may not try to define anything that's not an atom. | |
41 | ||
42 | | (define #f #t) | |
43 | | (display #f) | |
44 | ? illegal top-level form: (define #f #t) | |
45 | ||
46 | You may define multiple names. | |
47 | ||
48 | | (define true #t) | |
49 | | (define false #f) | |
50 | | (display false) | |
51 | | (display true) | |
52 | = #f | |
53 | = #t | |
54 | ||
55 | Names may not be redefined once defined. | |
56 | ||
57 | | (define true #t) | |
58 | | (define true #f) | |
59 | ? symbol already defined: true | |
60 | ||
61 | ### `reactor` ### | |
62 | ||
63 | `(reactor LIST-OF-ATOMS STATE-EXPR BODY-EXPR)` installs a reactor. Reactors | |
64 | permit the construction of interactive Robin programs. See the document | |
65 | `Reactor.markdown` for more information on, examples of, and tests for reactors. | |
66 | ||
67 | Intrinsic Data Types | |
68 | -------------------- | |
69 | ||
70 | ### S-expressions ### | |
71 | ||
72 | An S-expression is a sort of catch-all data type which includes | |
73 | all the other data types. It is inductively defined as follows: | |
74 | ||
75 | * A symbol is an S-expression. | |
76 | * A boolean is an S-expression. | |
77 | * An integer is an S-expression. | |
78 | * A macro is an S-expression. | |
79 | * An intrinsic is an S-expression. | |
80 | * An empty list is an S-expression. | |
81 | * A list cell containing an S-expression, prepended to another list, | |
82 | is an S-expression. | |
83 | * Nothing else is an S-expression. | |
84 | ||
85 | S-expressions have a textual representation, but not all types have values | |
86 | that can be directly expressed in this textual representation. All | |
87 | S-expressions have some meaning when interpeted as Robin programs, as | |
88 | defined by Robin's evaluation rules, but that meaning might be to | |
89 | raise an exception to indicate an error. | |
90 | ||
91 | ### Symbol ### | |
92 | ||
93 | A symbol is an atomic value represented by a string of characters | |
94 | which may not include whitespace or parentheses or a few other | |
95 | characters (TODO: decide which ones) and which may not begin with | |
96 | a `#` (pound sign) or a few other characters (TODO: decide which | |
97 | ones.) | |
98 | ||
99 | When in a Robin program proper, a symbol can be bound to a value, and | |
100 | in this context is it referred to as an _identifier_. However, if an | |
101 | attempt is made to evaluate a symbol which is not an identifier, | |
102 | an exception will be raised. For a symbol to appear unevaluated in a Robin | |
103 | program, it must be an argument to a macro. For that reason, we can't | |
104 | show an example of a literal symbol without first defining a macro... but | |
105 | will go ahead and show the example, and will explain macros later. | |
106 | ||
107 | | (define literal (@macro (self args env) (@head args))) | |
108 | | (display (literal hello)) | |
109 | = hello | |
110 | ||
111 | A Robin implementation is not expected to be able to generate new symbols | |
112 | at runtime. | |
113 | ||
114 | Symbols can be applied, and that is a typical use of them. But actually, | |
115 | it is what the symbol is bound to in the environment that is applied. | |
116 | ||
117 | ### Booleans ### | |
118 | ||
119 | There are two values of Boolean type, `#t`, representing truth, and `#f`, | |
120 | representing falsehood. By convention, an identifier which ends in `?` | |
121 | is a macro or function which evaluates to a Boolean. The `@if` intrinsic | |
122 | expects a Boolean expression as its first argument. | |
123 | ||
124 | Booleans always evaluate to themselves. | |
125 | ||
126 | | (display #t) | |
127 | = #t | |
128 | ||
129 | | (display #f) | |
130 | = #f | |
131 | ||
132 | Booleans cannot be applied. | |
133 | ||
134 | | (display (#t 1 2 3)) | |
135 | ? uncaught exception: (inapplicable-object #t) | |
136 | ||
137 | ### Integers ### | |
138 | ||
139 | An integer, in the context of Robin, is always a 32-bit signed integer. | |
140 | If you want larger integers or rational numbers, you'll need to build a | |
141 | bigint library or such. | |
142 | ||
143 | For example, 5 is an integer: | |
144 | ||
145 | | (display 5) | |
146 | = 5 | |
147 | ||
148 | Whereas 6167172726261721 is not, and you get the 32-bit signed integer | |
149 | equivalent: | |
150 | ||
151 | | (display 6167172726261721) | |
152 | = -878835751 | |
153 | ||
154 | Integers always evaluate to themselves. | |
155 | ||
156 | Integers cannot be applied. | |
157 | ||
158 | | (display (900 1 2 3)) | |
159 | ? uncaught exception: (inapplicable-object 900) | |
160 | ||
161 | ### Macros ### | |
162 | ||
163 | A macro is an S-expression, in an environment, which describes how to | |
164 | translate one S-expression to another. | |
165 | ||
166 | One area where Robin diverges significantly from Lisp and Scheme is that, | |
167 | whereas Lisp and Scheme support macro capabilities, in Robin, the macro | |
168 | is a **fundamental type**. Other abstractions, such as function values, are | |
169 | built on top of macros. Macros are "first-class" objects that may exist | |
170 | at runtime and can evaluate to other macros. Therefore, the word "macro" | |
171 | has, perhaps, a slightly different meaning in Robin than in Lisp or Scheme. | |
172 | ||
173 | They can also be compared to the one-argument `lambda` form from PicoLisp; | |
174 | again, however, unlike PicoLisp's variety of `lambda` forms, Robin's | |
175 | macros are the *only* abstraction of this kind fundamentally available, and | |
176 | other such abstractions *must* be built on top of macros. | |
177 | ||
178 | Whereas a function evaluates each of its arguments to values, and | |
179 | binds each of those values to a formal parameter of the function, then | |
180 | evaluates the body of the function in that new environment, a macro: | |
181 | ||
182 | * binds the macro value itself to the first formal parameter of the | |
183 | macro (by convention called `self`) — this is to facilitate writing | |
184 | recursive macros; | |
185 | * binds the literal tail of the list of the macro application to | |
186 | the second formal parameter of the macro (by convention called `args`); | |
187 | * binds a binding alist representing the environment in effect at the | |
188 | point the macro was evaluated to the third formal parameter (by | |
189 | convention called `env`); and | |
190 | * evaluates the body of the macro in that environment. | |
191 | ||
192 | Macros are defined with the `@macro` intrinsic. | |
193 | ||
194 | Macros evaluate to themselves. | |
195 | ||
196 | Macros are represented as the S-expression expansion of their | |
197 | implementation. | |
198 | ||
199 | | (display | |
200 | | (@macro (self args env) args)) | |
201 | = (@macro (self args env) args) | |
202 | ||
203 | Macros can be applied, and that is the typical use of them. | |
204 | ||
205 | ### Intrinsics ### | |
206 | ||
207 | (This section needs rewriting.) | |
208 | ||
209 | There also exist functions which cannot effectively be expressed directly | |
210 | in Robin — these are the so-called _intrinsics_. All symbols representing | |
211 | intrinsics directly begin with the character `@`. | |
212 | ||
213 | One important intrinsic is `@eval`. Many macros will make use of `eval`, | |
214 | to evaluate that literal tail they receive. When they do this in the | |
215 | environment in which they were called, they behave a lot like functions. | |
216 | But they are not obligated to; they might evaluate them in a modified | |
217 | environment, or not evaluate them at all and treat them as a literal | |
218 | S-expression. | |
219 | ||
220 | Intrinsics evaluate to themselves. | |
221 | ||
222 | An intrinsic is represented thusly. | |
223 | ||
224 | | (display @head) | |
225 | = @head | |
226 | ||
227 | One upshot of intrinsics is that all intrinsic Robin functionality | |
228 | (excepting top-level forms) can be passed around as values. | |
229 | ||
230 | | (display | |
231 | | (@prepend @if (@prepend @head ()))) | |
232 | = (@if @head) | |
233 | ||
234 | Intrinsics can be applied, and that is the typical use of them. | |
235 | ||
236 | ### Lists ### | |
237 | ||
238 | A list is either the empty list, or a list cell containing a value of any | |
239 | type, prepended to another list. | |
240 | ||
241 | The "head" of a list cell is the value (of any type) that it contains; | |
242 | the "tail" is the other list that it is prepended to. The empty list | |
243 | has neither head nor tail. | |
244 | ||
245 | Lists have a literal representation in Robin's S-expression based | |
246 | syntax. | |
247 | ||
248 | The empty list is notated `()` and it evaluates to itself. | |
249 | ||
250 | | (display | |
251 | | ()) | |
252 | = () | |
253 | ||
254 | A list with several elements is notated as a sequence of those | |
255 | elements, preceded by a `(`, followed by a `)`, and delimited | |
256 | by whitespace. | |
257 | ||
258 | Non-empty lists do not evaluate to themselves; rather, they represent a macro | |
259 | application. However, the `literal` macro may be used to obtain a | |
260 | literal list. | |
261 | ||
262 | | (define literal (@macro (s a e) (@head a))) | |
263 | | (display | |
264 | | (literal (7 8))) | |
265 | = (7 8) | |
266 | ||
267 | Lists cannot be directly applied, but since a list itself represents an | |
268 | application, that application is undertaken, and the result of it can | |
269 | be applied. | |
270 | ||
271 | Conventional Data Types | |
272 | ----------------------- | |
273 | ||
274 | This section lists data types that are not intrinsic, but are rather | |
275 | arrangements of intrinsic types in a way that follows a convention. | |
276 | ||
277 | ### Strings ### | |
278 | ||
279 | Strings are just lists of integers, where each integer refers to a | |
280 | particular Unicode codepoint. Robin supports a sugared syntax for | |
281 | specifying literal strings. The characters of the string are given | |
282 | between pairs of single quotes. | |
283 | ||
284 | | (define literal (@macro (s a e) (@head a))) | |
285 | | (display | |
286 | | (literal ''Hello'')) | |
287 | = (72 101 108 108 111) | |
288 | ||
289 | A single single quote may appear in string literals of this kind. | |
290 | ||
291 | | (define literal (@macro (s a e) (@head a))) | |
292 | | (display | |
293 | | (literal ''He'llo'')) | |
294 | = (72 101 39 108 108 111) | |
295 | ||
296 | Between the single quotes delimiting the string literal, a *sentinel* | |
297 | may be given. The sentinel between the leading single quote pair must | |
298 | match the sentinel given between the trailing single quote pair. The | |
299 | sentinel may consist of any text not containing a single quote. | |
300 | ||
301 | | (define literal (@macro (s a e) (@head a))) | |
302 | | (display | |
303 | | (literal 'X'Hello'X')) | |
304 | = (72 101 108 108 111) | |
305 | ||
306 | | (define literal (@macro (s a e) (@head a))) | |
307 | | (display | |
308 | | (literal '...@('Hello'...@(')) | |
309 | = (72 101 108 108 111) | |
310 | ||
311 | | (define literal (@macro (s a e) (@head a))) | |
312 | | (display | |
313 | | (literal 'X'Hello'Y')) | |
314 | ? unexpected end of input | |
315 | ||
316 | A sentinelized literal like this may embed a pair of single quotes. | |
317 | ||
318 | | (define literal (@macro (s a e) (@head a))) | |
319 | | (display | |
320 | | (literal 'X'Hel''lo'X')) | |
321 | = (72 101 108 39 39 108 111) | |
322 | ||
323 | By choosing different sentinels, string literals may contain any other | |
324 | string literal. | |
325 | ||
326 | | (define literal (@macro (s a e) (@head a))) | |
327 | | (display | |
328 | | (literal 'X'Hel'Y'bye'Y'lo'X')) | |
329 | = (72 101 108 39 89 39 98 121 101 39 89 39 108 111) | |
330 | ||
331 | No interpolation of escape sequences is done in a Robin string literal. | |
332 | (Functions to convert escape sequences commonly found in other languages | |
333 | may one day be available in a standard module.) | |
334 | ||
335 | | (define literal (@macro (s a e) (@head a))) | |
336 | | (display | |
337 | | (literal ''Hello\nworld'')) | |
338 | = (72 101 108 108 111 92 110 119 111 114 108 100) | |
339 | ||
340 | All characters which appear in the source text between the delimiters | |
341 | of the string literal are literally included in the string. | |
342 | ||
343 | | (define literal (@macro (s a e) (@head a))) | |
344 | | (display | |
345 | | (literal ''Hello | |
346 | | world'')) | |
347 | = (72 101 108 108 111 10 119 111 114 108 100) | |
348 | ||
349 | Adjacent string literals are not automatically concatenated. | |
350 | ||
351 | | (define literal (@macro (s a e) (@head a))) | |
352 | | (display | |
353 | | (literal (''Hello'' ''world''))) | |
354 | = ((72 101 108 108 111) (119 111 114 108 100)) | |
355 | ||
356 | ### Alists ### | |
357 | ||
358 | An alist, short for "association list", is simply a list of two-element | |
359 | sublists. The idea is that each of these two-elements associates, in some | |
360 | context, the value of its first element with the value of its second element. | |
361 | ||
362 | ### Binding Alists ### | |
363 | ||
364 | When the first element of each two-element sublist in an alist is a symbol, | |
365 | we call it a _binding alist_. The idea is that it is a Robin representation | |
366 | of an evaluation environment, where the symbols in the heads of the sublists | |
367 | are bound to the values in the tails of the pairs. Binding alists can be | |
368 | created from the environment currently in effect (such as in the case of the | |
369 | third argument of a macro) and can be used to change the evaluation | |
370 | environment that is in effect (such as in the first argument to `@eval`.) | |
371 | ||
372 | TODO: binding alists may be replaced by abstract map objects of some kind. | |
373 | ||
374 | Comments | |
375 | -------- | |
376 | ||
377 | Any S-expression preceded by a `;` symbol is a comment. It will still | |
378 | be parsed, but it will be ignored. | |
379 | ||
380 | | (display | |
381 | | ;(this program produces a list of two booleans) | |
382 | | (@prepend #f (@prepend #f ()))) | |
383 | = (#f #f) | |
384 | ||
385 | Because S-expressions may nest, and because comments may appear | |
386 | inside S-expressions, comments may nest. | |
387 | ||
388 | | (display | |
389 | | ;(this program produces | |
390 | | ;(what you might call) | |
391 | | a list of two booleans) | |
392 | | (@prepend #f (@prepend #f ()))) | |
393 | = (#f #f) | |
394 | ||
395 | Comments are still parsed. A syntax error in a comment is an error! | |
396 | ||
397 | | (display | |
398 | | ;(this program produces | |
399 | | #k | |
400 | | a list of booleans) | |
401 | | (prepend #f (prepend #f ()))) | |
402 | ? (line 3, column 6): | |
403 | ? unexpected "k" | |
404 | ? expecting "t" or "f" | |
405 | ||
406 | Any number of comments may appear together. | |
407 | ||
408 | | (display | |
409 | | (@prepend ;what ;on ;earth #f (@prepend #f ()))) | |
410 | = (#f #f) | |
411 | ||
412 | Comments may appear before a closing parenthesis. | |
413 | ||
414 | | (display | |
415 | | (@prepend #f (@prepend #f ()) ;foo)) | |
416 | = (#f #f) | |
417 | ||
418 | | (display | |
419 | | (@prepend #f (@prepend #f ()) ;peace ;(on) ;earth)) | |
420 | = (#f #f) | |
421 | ||
422 | Comments may appear in an empty list. | |
423 | ||
424 | | (display | |
425 | | ( ;hi ;there)) | |
426 | = () | |
427 | ||
428 | Comments need not be preceded by spaces. | |
429 | ||
430 | | (display | |
431 | | (;north;by;north;west)) | |
432 | = () | |
433 | ||
434 | To put truly arbitrary text in a comment, the string sugar syntax may be | |
435 | used. | |
436 | ||
437 | | (display | |
438 | | ;''This program, it produces a list of two booleans. #k ?'' | |
439 | | (@prepend #f (@prepend #f ()))) | |
440 | = (#f #f) |
0 | Robin | |
1 | ===== | |
2 | ||
3 | This document defines the fundamental semantics of Robin (except for the | |
4 | meanings of the intrinsics: see [Intrinsics.md](Intrinsics.md) for those.) | |
5 | ||
6 | -> Tests for functionality "Interpret core Robin Program" | |
7 | ||
8 | Top-level S-expressions | |
9 | ----------------------- | |
10 | ||
11 | A Robin program consists of a series of "top-level" S-expressions. | |
12 | Each top-level S-expression must have a particular form, but most of these | |
13 | top-level S-expressions may contain general, evaluatable S-expressions | |
14 | themselves. Allowable top-level forms are given in the subsections below. | |
15 | ||
16 | ### `display` ### | |
17 | ||
18 | `(display EXPR)` evaluates the EXPR and displays the result in a canonical | |
19 | S-expression rendering, followed by a newline. | |
20 | ||
21 | | (display #t) | |
22 | = #t | |
23 | ||
24 | Note that a Robin program may be split over several files in the filesystem. | |
25 | Also, more than one top-level S-expression may appear in a single file. | |
26 | ||
27 | | (display #t) | |
28 | | (display #f) | |
29 | = #t | |
30 | = #f | |
31 | ||
32 | ### `define` ### | |
33 | ||
34 | `(define ATOM EXPR)` defines a global name. | |
35 | ||
36 | | (define true #t) | |
37 | | (display true) | |
38 | = #t | |
39 | ||
40 | You may not try to define anything that's not an atom. | |
41 | ||
42 | | (define #f #t) | |
43 | | (display #f) | |
44 | ? illegal top-level form: (define #f #t) | |
45 | ||
46 | You may define multiple names. | |
47 | ||
48 | | (define true #t) | |
49 | | (define false #f) | |
50 | | (display false) | |
51 | | (display true) | |
52 | = #f | |
53 | = #t | |
54 | ||
55 | Names may not be redefined once defined. | |
56 | ||
57 | | (define true #t) | |
58 | | (define true #f) | |
59 | ? symbol already defined: true | |
60 | ||
61 | ### `reactor` ### | |
62 | ||
63 | `(reactor LIST-OF-ATOMS STATE-EXPR BODY-EXPR)` installs a reactor. Reactors | |
64 | permit the construction of interactive Robin programs. See the document | |
65 | [Reactor.md](Reactor.md) for more information on, examples of, and tests for reactors. | |
66 | ||
67 | Intrinsic Data Types | |
68 | -------------------- | |
69 | ||
70 | ### S-expressions ### | |
71 | ||
72 | An S-expression is a sort of catch-all data type which includes | |
73 | all the other data types. It is inductively defined as follows: | |
74 | ||
75 | * A symbol is an S-expression. | |
76 | * A boolean is an S-expression. | |
77 | * An integer is an S-expression. | |
78 | * A macro is an S-expression. | |
79 | * An intrinsic is an S-expression. | |
80 | * An empty list is an S-expression. | |
81 | * A list cell containing an S-expression, prepended to another list, | |
82 | is an S-expression. | |
83 | * Nothing else is an S-expression. | |
84 | ||
85 | S-expressions have a textual representation, but not all types have values | |
86 | that can be directly expressed in this textual representation. All | |
87 | S-expressions have some meaning when interpeted as Robin programs, as | |
88 | defined by Robin's evaluation rules, but that meaning might be to | |
89 | raise an exception to indicate an error. | |
90 | ||
91 | ### Symbol ### | |
92 | ||
93 | A symbol is an atomic value represented by a string of characters | |
94 | which may not include whitespace or parentheses or a few other | |
95 | characters (TODO: decide which ones) and which may not begin with | |
96 | a `#` (pound sign) or a few other characters (TODO: decide which | |
97 | ones.) | |
98 | ||
99 | When in a Robin program proper, a symbol can be bound to a value, and | |
100 | in this context is it referred to as an _identifier_. However, if an | |
101 | attempt is made to evaluate a symbol which is not an identifier, | |
102 | an exception will be raised. For a symbol to appear unevaluated in a Robin | |
103 | program, it must be an argument to a macro. For that reason, we can't | |
104 | show an example of a literal symbol without first defining a macro... but | |
105 | will go ahead and show the example, and will explain macros later. | |
106 | ||
107 | | (define literal (@macro (self args env) (@head args))) | |
108 | | (display (literal hello)) | |
109 | = hello | |
110 | ||
111 | A Robin implementation is not expected to be able to generate new symbols | |
112 | at runtime. | |
113 | ||
114 | Symbols can be applied, and that is a typical use of them. But actually, | |
115 | it is what the symbol is bound to in the environment that is applied. | |
116 | ||
117 | ### Booleans ### | |
118 | ||
119 | There are two values of Boolean type, `#t`, representing truth, and `#f`, | |
120 | representing falsehood. By convention, an identifier which ends in `?` | |
121 | is a macro or function which evaluates to a Boolean. The `@if` intrinsic | |
122 | expects a Boolean expression as its first argument. | |
123 | ||
124 | Booleans always evaluate to themselves. | |
125 | ||
126 | | (display #t) | |
127 | = #t | |
128 | ||
129 | | (display #f) | |
130 | = #f | |
131 | ||
132 | Booleans cannot be applied. | |
133 | ||
134 | | (display (#t 1 2 3)) | |
135 | ? uncaught exception: (inapplicable-object #t) | |
136 | ||
137 | ### Integers ### | |
138 | ||
139 | An integer, in the context of Robin, is always a 32-bit signed integer. | |
140 | If you want larger integers or rational numbers, you'll need to build a | |
141 | bigint library or such. | |
142 | ||
143 | For example, 5 is an integer: | |
144 | ||
145 | | (display 5) | |
146 | = 5 | |
147 | ||
148 | Whereas 6167172726261721 is not, and you get the 32-bit signed integer | |
149 | equivalent: | |
150 | ||
151 | | (display 6167172726261721) | |
152 | = -878835751 | |
153 | ||
154 | Integers always evaluate to themselves. | |
155 | ||
156 | Integers cannot be applied. | |
157 | ||
158 | | (display (900 1 2 3)) | |
159 | ? uncaught exception: (inapplicable-object 900) | |
160 | ||
161 | ### Macros ### | |
162 | ||
163 | A macro is an S-expression, in an environment, which describes how to | |
164 | translate one S-expression to another. | |
165 | ||
166 | One area where Robin diverges significantly from Lisp and Scheme is that, | |
167 | whereas Lisp and Scheme support macro capabilities, in Robin, the macro | |
168 | is a **fundamental type**. Other abstractions, such as function values, are | |
169 | built on top of macros. Macros are "first-class" objects that may exist | |
170 | at runtime and can evaluate to other macros. Therefore, the word "macro" | |
171 | has, perhaps, a slightly different meaning in Robin than in Lisp or Scheme. | |
172 | ||
173 | They can also be compared to the one-argument `lambda` form from PicoLisp; | |
174 | again, however, unlike PicoLisp's variety of `lambda` forms, Robin's | |
175 | macros are the *only* abstraction of this kind fundamentally available, and | |
176 | other such abstractions *must* be built on top of macros. | |
177 | ||
178 | Whereas a function evaluates each of its arguments to values, and | |
179 | binds each of those values to a formal parameter of the function, then | |
180 | evaluates the body of the function in that new environment, a macro: | |
181 | ||
182 | * binds the macro value itself to the first formal parameter of the | |
183 | macro (by convention called `self`) — this is to facilitate writing | |
184 | recursive macros; | |
185 | * binds the literal tail of the list of the macro application to | |
186 | the second formal parameter of the macro (by convention called `args`); | |
187 | * binds a binding alist representing the environment in effect at the | |
188 | point the macro was evaluated to the third formal parameter (by | |
189 | convention called `env`); and | |
190 | * evaluates the body of the macro in that environment. | |
191 | ||
192 | Macros are defined with the `@macro` intrinsic. | |
193 | ||
194 | Macros evaluate to themselves. | |
195 | ||
196 | Macros are represented as the S-expression expansion of their | |
197 | implementation. | |
198 | ||
199 | | (display | |
200 | | (@macro (self args env) args)) | |
201 | = (@macro (self args env) args) | |
202 | ||
203 | Macros can be applied, and that is the typical use of them. | |
204 | ||
205 | ### Intrinsics ### | |
206 | ||
207 | (This section needs rewriting.) | |
208 | ||
209 | There also exist functions which cannot effectively be expressed directly | |
210 | in Robin — these are the so-called _intrinsics_. All symbols representing | |
211 | intrinsics directly begin with the character `@`. | |
212 | ||
213 | One important intrinsic is `@eval`. Many macros will make use of `eval`, | |
214 | to evaluate that literal tail they receive. When they do this in the | |
215 | environment in which they were called, they behave a lot like functions. | |
216 | But they are not obligated to; they might evaluate them in a modified | |
217 | environment, or not evaluate them at all and treat them as a literal | |
218 | S-expression. | |
219 | ||
220 | Intrinsics evaluate to themselves. | |
221 | ||
222 | An intrinsic is represented thusly. | |
223 | ||
224 | | (display @head) | |
225 | = @head | |
226 | ||
227 | One upshot of intrinsics is that all intrinsic Robin functionality | |
228 | (excepting top-level forms) can be passed around as values. | |
229 | ||
230 | | (display | |
231 | | (@prepend @if (@prepend @head ()))) | |
232 | = (@if @head) | |
233 | ||
234 | Intrinsics can be applied, and that is the typical use of them. | |
235 | ||
236 | ### Lists ### | |
237 | ||
238 | A list is either the empty list, or a list cell containing a value of any | |
239 | type, prepended to another list. | |
240 | ||
241 | The "head" of a list cell is the value (of any type) that it contains; | |
242 | the "tail" is the other list that it is prepended to. The empty list | |
243 | has neither head nor tail. | |
244 | ||
245 | Lists have a literal representation in Robin's S-expression based | |
246 | syntax. | |
247 | ||
248 | The empty list is notated `()` and it evaluates to itself. | |
249 | ||
250 | | (display | |
251 | | ()) | |
252 | = () | |
253 | ||
254 | A list with several elements is notated as a sequence of those | |
255 | elements, preceded by a `(`, followed by a `)`, and delimited | |
256 | by whitespace. | |
257 | ||
258 | Non-empty lists do not evaluate to themselves; rather, they represent a macro | |
259 | application. However, the `literal` macro may be used to obtain a | |
260 | literal list. | |
261 | ||
262 | | (define literal (@macro (s a e) (@head a))) | |
263 | | (display | |
264 | | (literal (7 8))) | |
265 | = (7 8) | |
266 | ||
267 | Lists cannot be directly applied, but since a list itself represents an | |
268 | application, that application is undertaken, and the result of it can | |
269 | be applied. | |
270 | ||
271 | Conventional Data Types | |
272 | ----------------------- | |
273 | ||
274 | This section lists data types that are not intrinsic, but are rather | |
275 | arrangements of intrinsic types in a way that follows a convention. | |
276 | ||
277 | ### Strings ### | |
278 | ||
279 | Strings are just lists of integers, where each integer refers to a | |
280 | particular Unicode codepoint. Robin supports a sugared syntax for | |
281 | specifying literal strings. The characters of the string are given | |
282 | between pairs of single quotes. | |
283 | ||
284 | | (define literal (@macro (s a e) (@head a))) | |
285 | | (display | |
286 | | (literal ''Hello'')) | |
287 | = (72 101 108 108 111) | |
288 | ||
289 | A single single quote may appear in string literals of this kind. | |
290 | ||
291 | | (define literal (@macro (s a e) (@head a))) | |
292 | | (display | |
293 | | (literal ''He'llo'')) | |
294 | = (72 101 39 108 108 111) | |
295 | ||
296 | Between the single quotes delimiting the string literal, a *sentinel* | |
297 | may be given. The sentinel between the leading single quote pair must | |
298 | match the sentinel given between the trailing single quote pair. The | |
299 | sentinel may consist of any text not containing a single quote. | |
300 | ||
301 | | (define literal (@macro (s a e) (@head a))) | |
302 | | (display | |
303 | | (literal 'X'Hello'X')) | |
304 | = (72 101 108 108 111) | |
305 | ||
306 | | (define literal (@macro (s a e) (@head a))) | |
307 | | (display | |
308 | | (literal '...@('Hello'...@(')) | |
309 | = (72 101 108 108 111) | |
310 | ||
311 | | (define literal (@macro (s a e) (@head a))) | |
312 | | (display | |
313 | | (literal 'X'Hello'Y')) | |
314 | ? unexpected end of input | |
315 | ||
316 | A sentinelized literal like this may embed a pair of single quotes. | |
317 | ||
318 | | (define literal (@macro (s a e) (@head a))) | |
319 | | (display | |
320 | | (literal 'X'Hel''lo'X')) | |
321 | = (72 101 108 39 39 108 111) | |
322 | ||
323 | By choosing different sentinels, string literals may contain any other | |
324 | string literal. | |
325 | ||
326 | | (define literal (@macro (s a e) (@head a))) | |
327 | | (display | |
328 | | (literal 'X'Hel'Y'bye'Y'lo'X')) | |
329 | = (72 101 108 39 89 39 98 121 101 39 89 39 108 111) | |
330 | ||
331 | No interpolation of escape sequences is done in a Robin string literal. | |
332 | (Functions to convert escape sequences commonly found in other languages | |
333 | may one day be available in a standard module.) | |
334 | ||
335 | | (define literal (@macro (s a e) (@head a))) | |
336 | | (display | |
337 | | (literal ''Hello\nworld'')) | |
338 | = (72 101 108 108 111 92 110 119 111 114 108 100) | |
339 | ||
340 | All characters which appear in the source text between the delimiters | |
341 | of the string literal are literally included in the string. | |
342 | ||
343 | | (define literal (@macro (s a e) (@head a))) | |
344 | | (display | |
345 | | (literal ''Hello | |
346 | | world'')) | |
347 | = (72 101 108 108 111 10 119 111 114 108 100) | |
348 | ||
349 | Adjacent string literals are not automatically concatenated. | |
350 | ||
351 | | (define literal (@macro (s a e) (@head a))) | |
352 | | (display | |
353 | | (literal (''Hello'' ''world''))) | |
354 | = ((72 101 108 108 111) (119 111 114 108 100)) | |
355 | ||
356 | ### Alists ### | |
357 | ||
358 | An alist, short for "association list", is simply a list of two-element | |
359 | sublists. The idea is that each of these two-elements associates, in some | |
360 | context, the value of its first element with the value of its second element. | |
361 | ||
362 | ### Binding Alists ### | |
363 | ||
364 | When the first element of each two-element sublist in an alist is a symbol, | |
365 | we call it a _binding alist_. The idea is that it is a Robin representation | |
366 | of an evaluation environment, where the symbols in the heads of the sublists | |
367 | are bound to the values in the tails of the pairs. Binding alists can be | |
368 | created from the environment currently in effect (such as in the case of the | |
369 | third argument of a macro) and can be used to change the evaluation | |
370 | environment that is in effect (such as in the first argument to `@eval`.) | |
371 | ||
372 | TODO: binding alists may be replaced by abstract map objects of some kind. | |
373 | ||
374 | Comments | |
375 | -------- | |
376 | ||
377 | Any S-expression preceded by a `;` symbol is a comment. It will still | |
378 | be parsed, but it will be ignored. | |
379 | ||
380 | | (display | |
381 | | ;(this program produces a list of two booleans) | |
382 | | (@prepend #f (@prepend #f ()))) | |
383 | = (#f #f) | |
384 | ||
385 | Because S-expressions may nest, and because comments may appear | |
386 | inside S-expressions, comments may nest. | |
387 | ||
388 | | (display | |
389 | | ;(this program produces | |
390 | | ;(what you might call) | |
391 | | a list of two booleans) | |
392 | | (@prepend #f (@prepend #f ()))) | |
393 | = (#f #f) | |
394 | ||
395 | Comments are still parsed. A syntax error in a comment is an error! | |
396 | ||
397 | | (display | |
398 | | ;(this program produces | |
399 | | #k | |
400 | | a list of booleans) | |
401 | | (prepend #f (prepend #f ()))) | |
402 | ? (line 3, column 6): | |
403 | ? unexpected "k" | |
404 | ? expecting "t" or "f" | |
405 | ||
406 | Any number of comments may appear together. | |
407 | ||
408 | | (display | |
409 | | (@prepend ;what ;on ;earth #f (@prepend #f ()))) | |
410 | = (#f #f) | |
411 | ||
412 | Comments may appear before a closing parenthesis. | |
413 | ||
414 | | (display | |
415 | | (@prepend #f (@prepend #f ()) ;foo)) | |
416 | = (#f #f) | |
417 | ||
418 | | (display | |
419 | | (@prepend #f (@prepend #f ()) ;peace ;(on) ;earth)) | |
420 | = (#f #f) | |
421 | ||
422 | Comments may appear in an empty list. | |
423 | ||
424 | | (display | |
425 | | ( ;hi ;there)) | |
426 | = () | |
427 | ||
428 | Comments need not be preceded by spaces. | |
429 | ||
430 | | (display | |
431 | | (;north;by;north;west)) | |
432 | = () | |
433 | ||
434 | To put truly arbitrary text in a comment, the string sugar syntax may be | |
435 | used. | |
436 | ||
437 | | (display | |
438 | | ;''This program, it produces a list of two booleans. #k ?'' | |
439 | | (@prepend #f (@prepend #f ()))) | |
440 | = (#f #f) |
0 | -> Functionality "Interpret core Robin Program" is implemented by shell command | |
1 | -> "bin/robinri %(test-body-file)" | |
2 | ||
3 | -> Functionality "Interpret Robin Program" is implemented by shell command | |
4 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin %(test-body-file)" | |
5 | ||
6 | -> Functionality "Interpret Robin Program (with Small)" is implemented by shell command | |
7 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin %(test-body-file)" | |
8 | ||
9 | -> Functionality "Interpret Robin Program (with Fun)" is implemented by shell command | |
10 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin %(test-body-file)" | |
11 | ||
12 | -> Functionality "Interpret Robin Program (with List)" is implemented by shell command | |
13 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin %(test-body-file)" | |
14 | ||
15 | -> Functionality "Interpret Robin Program (with Env)" is implemented by shell command | |
16 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin pkg/env.robin %(test-body-file)" | |
17 | ||
18 | -> Functionality "Interpret Robin Program (with Boolean)" is implemented by shell command | |
19 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/boolean.robin %(test-body-file)" | |
20 | ||
21 | -> Functionality "Interpret Robin Program (with Arith)" is implemented by shell command | |
22 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/arith.robin %(test-body-file)" | |
23 | ||
24 | -> Functionality "Interpret Robin Program (with List-Arith)" is implemented by shell command | |
25 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin pkg/arith.robin pkg/list-arith.robin %(test-body-file)" | |
26 | ||
27 | -> Functionality "Interpret Robin Program (with Stdlib)" is implemented by shell command | |
28 | -> "bin/robinri pkg/stdlib.robin %(test-body-file)" |
0 | -> Functionality "Interpret core Robin Program" is implemented by shell command | |
1 | -> "bin/robinri %(test-body-file)" | |
2 | ||
3 | -> Functionality "Interpret Robin Program" is implemented by shell command | |
4 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin %(test-body-file)" | |
5 | ||
6 | -> Functionality "Interpret Robin Program (with Small)" is implemented by shell command | |
7 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin %(test-body-file)" | |
8 | ||
9 | -> Functionality "Interpret Robin Program (with Fun)" is implemented by shell command | |
10 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin %(test-body-file)" | |
11 | ||
12 | -> Functionality "Interpret Robin Program (with List)" is implemented by shell command | |
13 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin %(test-body-file)" | |
14 | ||
15 | -> Functionality "Interpret Robin Program (with Env)" is implemented by shell command | |
16 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin pkg/env.robin %(test-body-file)" | |
17 | ||
18 | -> Functionality "Interpret Robin Program (with Boolean)" is implemented by shell command | |
19 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/boolean.robin %(test-body-file)" | |
20 | ||
21 | -> Functionality "Interpret Robin Program (with Arith)" is implemented by shell command | |
22 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/arith.robin %(test-body-file)" | |
23 | ||
24 | -> Functionality "Interpret Robin Program (with List-Arith)" is implemented by shell command | |
25 | -> "bin/robinri pkg/small.robin pkg/intrinsics-wrappers.robin pkg/fun.robin pkg/list.robin pkg/arith.robin pkg/list-arith.robin %(test-body-file)" | |
26 | ||
27 | -> Functionality "Interpret Robin Program (with Stdlib)" is implemented by shell command | |
28 | -> "bin/robinri pkg/stdlib.robin %(test-body-file)" |
0 | -> Functionality "Interpret core Robin Program" is implemented by shell command | |
1 | -> "bin/whitecap --no-builtins %(test-body-file)" | |
2 | ||
3 | -> Functionality "Interpret Robin Program" is implemented by shell command | |
4 | -> "bin/whitecap %(test-body-file)" | |
5 | ||
6 | -> Functionality "Interpret Robin Program (with Small)" is implemented by shell command | |
7 | -> "bin/whitecap %(test-body-file)" | |
8 | ||
9 | -> Functionality "Interpret Robin Program (with Fun)" is implemented by shell command | |
10 | -> "bin/whitecap %(test-body-file)" | |
11 | ||
12 | -> Functionality "Interpret Robin Program (with List)" is implemented by shell command | |
13 | -> "bin/whitecap pkg/list.robin %(test-body-file)" | |
14 | ||
15 | -> Functionality "Interpret Robin Program (with Boolean)" is implemented by shell command | |
16 | -> "bin/whitecap pkg/boolean.robin %(test-body-file)" | |
17 | ||
18 | -> Functionality "Interpret Robin Program (with Env)" is implemented by shell command | |
19 | -> "bin/whitecap pkg/list.robin pkg/env.robin %(test-body-file)" | |
20 | ||
21 | -> Functionality "Interpret Robin Program (with Arith)" is implemented by shell command | |
22 | -> "bin/whitecap pkg/arith.robin %(test-body-file)" | |
23 | ||
24 | -> Functionality "Interpret Robin Program (with List-Arith)" is implemented by shell command | |
25 | -> "bin/whitecap pkg/list.robin pkg/arith.robin pkg/list-arith.robin %(test-body-file)" | |
26 | ||
27 | -> Functionality "Interpret Robin Program (with Stdlib)" is implemented by shell command | |
28 | -> "bin/whitecap pkg/stdlib-for-robini.robin %(test-body-file)" |
0 | -> Functionality "Interpret core Robin Program" is implemented by shell command | |
1 | -> "bin/whitecap --no-builtins %(test-body-file)" | |
2 | ||
3 | -> Functionality "Interpret Robin Program" is implemented by shell command | |
4 | -> "bin/whitecap %(test-body-file)" | |
5 | ||
6 | -> Functionality "Interpret Robin Program (with Small)" is implemented by shell command | |
7 | -> "bin/whitecap %(test-body-file)" | |
8 | ||
9 | -> Functionality "Interpret Robin Program (with Fun)" is implemented by shell command | |
10 | -> "bin/whitecap %(test-body-file)" | |
11 | ||
12 | -> Functionality "Interpret Robin Program (with List)" is implemented by shell command | |
13 | -> "bin/whitecap pkg/list.robin %(test-body-file)" | |
14 | ||
15 | -> Functionality "Interpret Robin Program (with Boolean)" is implemented by shell command | |
16 | -> "bin/whitecap pkg/boolean.robin %(test-body-file)" | |
17 | ||
18 | -> Functionality "Interpret Robin Program (with Env)" is implemented by shell command | |
19 | -> "bin/whitecap pkg/list.robin pkg/env.robin %(test-body-file)" | |
20 | ||
21 | -> Functionality "Interpret Robin Program (with Arith)" is implemented by shell command | |
22 | -> "bin/whitecap pkg/arith.robin %(test-body-file)" | |
23 | ||
24 | -> Functionality "Interpret Robin Program (with List-Arith)" is implemented by shell command | |
25 | -> "bin/whitecap pkg/list.robin pkg/arith.robin pkg/list-arith.robin %(test-body-file)" | |
26 | ||
27 | -> Functionality "Interpret Robin Program (with Stdlib)" is implemented by shell command | |
28 | -> "bin/whitecap pkg/stdlib-for-robini.robin %(test-body-file)" |
2 | 2 | ./build.sh || exit 1 |
3 | 3 | |
4 | 4 | TESTDOCS1=" |
5 | doc/Robin.markdown | |
6 | doc/Intrinsics.markdown | |
7 | doc/Reactor.markdown | |
5 | doc/Robin.md | |
6 | doc/Intrinsics.md | |
7 | doc/Reactor.md | |
8 | 8 | " |
9 | 9 | |
10 | 10 | if [ "${FIXTURE}x" = "x" ]; then |
11 | FIXTURE=fixture/whitecap.markdown | |
11 | FIXTURE=fixture/whitecap.md | |
12 | 12 | fi |
13 | 13 | echo "Using fixture $FIXTURE..." |
14 | 14 | |
20 | 20 | falderal -b $FIXTURE pkg/$PACKAGE.robin || exit 1 |
21 | 21 | done |
22 | 22 | |
23 | rm -f config.markdown | |
23 | rm -f config.md |