Move Reactor documentation into the main specification document.
Chris Pressey
5 years ago
46 | 46 | Robin 0.2 *adds* to Robin 0.1: |
47 | 47 | |
48 | 48 | * _reactors_, which I hope will be a cleaner and more system-agnostic |
49 | way to do I/O. See [doc/Reactor.md](doc/Reactor.md). | |
49 | way to do I/O. | |
50 | 50 | |
51 | 51 | Robin 0.1 (ca 2012) |
52 | 52 | --------- |
110 | 110 | |
111 | 111 | * [doc/Tutorial.md](doc/Tutorial.md) — Robin tutorial. |
112 | 112 | * [doc/Robin.md](doc/Robin.md) — core language specification. |
113 | * [doc/Reactor.md](doc/Reactor.md) — reactors specification. | |
114 | * [doc/Modules.md](doc/Modules.md) — description of modules. | |
113 | * [doc/Modules.md](doc/Modules.md) — ideas about modules. | |
115 | 114 | * [stdlib/](stdlib/) — normative definitions of standard symbols. |
116 | 115 | * [HISTORY.md](HISTORY.md) — history of the language. |
117 | 116 | * [TODO.md](TODO.md) — plans. |
0 | 0 | TODO |
1 | 1 | ==== |
2 | 2 | |
3 | See if the deleted tests from the instrinsics-wrappers | |
4 | need to be restored. | |
5 | ||
6 | Bring together the other parts of the docs into a single large spec. | |
7 | ||
8 | Long-term | |
9 | --------- | |
3 | (some of these are possibly long-term plans) | |
10 | 4 | |
11 | 5 | A way to evaluate a Robin expression and display it, mainly |
12 | 6 | to make the tests more concise - don't need to say `(display ...)` always. |
0 | Robin Reactors | |
1 | ============== | |
2 | ||
3 | To separate the concerns of computation and interaction, Robin provides | |
4 | a construct called a _reactor_. While evaluation of a Robin expression | |
5 | accomplishes side-effect-free computation, reactors permit the construction | |
6 | of so-called "reactive" programs, which are driven by events and may | |
7 | interact with a user, a remote server on a network, or other source of | |
8 | events. Reactors are similar to event handlers in Javascript, or to | |
9 | processes in Erlang. | |
10 | ||
11 | In Robin 0.3, a reactor is installed by giving a top-level form with the | |
12 | following syntax: | |
13 | ||
14 | (reactor SUBSCRIPTIONS INITIAL-STATE-EXPR TRANSDUCER) | |
15 | ||
16 | The first argument of the `reactor` form is a literal (unevaluated) list | |
17 | of symbols, called the _subscriptions_ for the reactor. Each symbol names | |
18 | a _facility_ with which the reactor wishes to be able to interact. | |
19 | ||
20 | The second argument is evaluated, and becomes the _initial state_ of the | |
21 | reactor. | |
22 | ||
23 | The third argument of the `reactor` form is evaluated to obtain a | |
24 | macro. This is called the _transducer_ of the reactor. | |
25 | ||
26 | Whenever an event of interest to the reactor occurs, the transducer is | |
27 | evaluated, being passed two (pre-evaluated) arguments: | |
28 | ||
29 | * A two-element list called the _event_. The elements are: | |
30 | * A literal symbol called the _event type_, specifying what kind of event | |
31 | happened. | |
32 | * An arbitrary value called the _event payload_ containing more data about | |
33 | the event, in a format specific to the type of event. | |
34 | * The current state of the reactor. (This will be the initial state | |
35 | if the reactor body has never before been evaluated.) | |
36 | ||
37 | Given these things, the transducer is expected to evaluate to a list where the | |
38 | first element is the new state of the reactor, and each of the subsequent | |
39 | elements is an _command_, which is itself a two-element list containing: | |
40 | ||
41 | * A literal symbol called the _command type_ specifying the kind of | |
42 | command that is being requested to be executed; and | |
43 | * An arbitrary value called the _command payload_ containing more data | |
44 | about the command, in a format specific to that type of command. | |
45 | ||
46 | There may of course be zero commands in the returned list, but it is an | |
47 | error if the returned value is not a list containing at least one element. | |
48 | ||
49 | If the transducer throws an error, no commands will be executed, and | |
50 | the state of the transducer will remain unchanged. Implementations | |
51 | should allow such occurrences to be visible and/or logged. | |
52 | ||
53 | In fact, commands _are_ events. We just call them commands when it is | |
54 | a reactor producing them, and events when a reactor is receiving them. | |
55 | ||
56 | Standard Events | |
57 | --------------- | |
58 | ||
59 | ### `init` ### | |
60 | ||
61 | When a reactor first starts up it will receive an event telling it that | |
62 | it has started up. The event type for this event is the literal symbol | |
63 | `init`. | |
64 | ||
65 | There are two things a reactor will almost always want to do when it | |
66 | receives an `init` event: establish a known initial state, and | |
67 | subscribe to facilities. | |
68 | ||
69 | It establishes a known initial state by returning an initial state | |
70 | value as the first element of the list it returns. | |
71 | ||
72 | It subscribes to facilities by returning commands which request | |
73 | subscription to those facilities. Once subscribed, it will receive | |
74 | `subscribed` events from them. If the facility is not available, it | |
75 | will receive a `not-available` event. It may then elect to abort, | |
76 | or choose an alternate facility, or so forth. | |
77 | ||
78 | Standard Commands | |
79 | ----------------- | |
80 | ||
81 | ### `stop` ### | |
82 | ||
83 | Stops the current reactor, and removes it from the list of active | |
84 | reactors. It will no longer receive any events. | |
85 | ||
86 | Standard Facilities | |
87 | ------------------- | |
88 | ||
89 | If a reactor isn't subscribed to any facilities, it won't necessarily | |
90 | receive any events, although this is implementation-specific. | |
91 | ||
92 | The set of facilities is expected to be largely implementation-specific. | |
93 | ||
94 | All the same, there will probably be a set of standard facilities. | |
95 | ||
96 | Let's describe one such facility for concreteness. | |
97 | ||
98 | Facility `line-terminal` | |
99 | ------------------------ | |
100 | ||
101 | -> Tests for functionality "Interpret Robin Program (with Small)" | |
102 | ||
103 | The `line-terminal` facility allows a Robin program to interact with | |
104 | something or someone over a line-oriented protocol, similar to what | |
105 | "standard I/O" routed to a terminal gets you under Unix. Note that | |
106 | this is not guaranteed to be the "real" standard I/O; it could well be | |
107 | simulated with modal dialogue boxes in a GUI, or with textareas on a web | |
108 | page under Javascript, or so forth. | |
109 | ||
110 | The `line-terminal` facility understands commands of the form | |
111 | ||
112 | (writeln <STRING>) | |
113 | ||
114 | The `<STRING>` argument should be a Robin string (list of integers). Those | |
115 | integers, as bytes, are sent to whetever is listening on the other end of | |
116 | the line terminal. When attached to an actual terminal console (whether real | |
117 | or emulated), this would typically cause an ASCII representation of those bytes | |
118 | to be displayed. | |
119 | ||
120 | Knowing this, we can write a "Hello, world!" program. To keep it | |
121 | simple, we'll simply assume the line-terminal facility exists. | |
122 | We won't bother to subscribe to it. In addition, note that this reactor | |
123 | essentially doesn't keep any state — the initial state of the reactor is simply | |
124 | the integer 0, and the state is set to 0 after each event is reacted to. | |
125 | ||
126 | | (reactor (line-terminal) 0 | |
127 | | (macro (self args env) | |
128 | | (bind event (head args) | |
129 | | (bind event-type (head event) | |
130 | | (if (equal? event-type (literal init)) | |
131 | | (list 0 | |
132 | | (list (literal writeln) (literal ''Hello, world!'')) | |
133 | | (list (literal stop) 0)) | |
134 | | (list 0)))))) | |
135 | = Hello, world! | |
136 | ||
137 | Reactors which interact with `line-terminal` receive `readln` events. | |
138 | ||
139 | `readln`, is sent when a line of text is received | |
140 | on the "standard input". The payload for this event is a Robin string | |
141 | of the line of text received. This string does not contain any end-of-line | |
142 | marker characters. | |
143 | ||
144 | Thus we can construct a simple `cat` program: | |
145 | ||
146 | | (reactor (line-terminal) 0 | |
147 | | (macro (self args env) | |
148 | | (bind event (head args) | |
149 | | (bind event-type (head event) | |
150 | | (bind event-payload (head (tail event)) | |
151 | | (if (equal? event-type (literal readln)) | |
152 | | (list 0 | |
153 | | (list (literal writeln) event-payload)) | |
154 | | (list 0))))))) | |
155 | + Cat | |
156 | + Dog | |
157 | = Cat | |
158 | = Dog | |
159 | ||
160 | General Reactor properties | |
161 | -------------------------- | |
162 | ||
163 | A reactor can issue multiple commands in its response to an event. | |
164 | ||
165 | | (reactor (line-terminal) 0 | |
166 | | (macro (self args env) | |
167 | | (bind event (head args) | |
168 | | (bind event-type (head event) | |
169 | | (bind event-payload (head (tail event)) | |
170 | | (if (equal? event-type (literal readln)) | |
171 | | (list 0 | |
172 | | (list (literal writeln) (literal ''Line:'')) | |
173 | | (list (literal writeln) event-payload)) | |
174 | | (list 0))))))) | |
175 | + Cat | |
176 | + Dog | |
177 | = Line: | |
178 | = Cat | |
179 | = Line: | |
180 | = Dog | |
181 | ||
182 | When receiving a malformed command, a facility may produce a warning | |
183 | message of some kind, but it should otherwise ignore it and keep going. | |
184 | ||
185 | | (reactor (line-terminal) 0 | |
186 | | (macro (self args env) | |
187 | | (bind event (head args) | |
188 | | (bind event-type (head event) | |
189 | | (bind event-payload (head (tail event)) | |
190 | | (if (equal? event-type (literal readln)) | |
191 | | (list 0 | |
192 | | (literal what-is-this) | |
193 | | (literal i-dont-even) | |
194 | | (list (literal writeln) event-payload)) | |
195 | | (list 0))))))) | |
196 | + Cat | |
197 | + Dog | |
198 | = Cat | |
199 | = Dog | |
200 | ||
201 | If evaluating the transducer of a reactor raises an error, the reactor | |
202 | remains in the same state and issues no commands, but always recovers | |
203 | so that it can continue to handle subsequent events (i.e. it does not crash). | |
204 | ||
205 | An implementation is encouraged to allow these to be logged (and the | |
206 | reference implementation will display them if `--show-events` is given) | |
207 | but this is not a strict requirement. | |
208 | ||
209 | | (reactor (line-terminal) 0 | |
210 | | (macro (self args env) | |
211 | | (bind event (head args) | |
212 | | (bind event-type (head event) | |
213 | | (bind event-payload (head (tail event)) | |
214 | | (if (equal? event-type (literal readln)) | |
215 | | (if (equal? (head event-payload) 65) | |
216 | | (raise 999999) | |
217 | | (list 0 (list (literal writeln) event-payload))) | |
218 | | (list 0))))))) | |
219 | + Cat | |
220 | + Dog | |
221 | + Alligator | |
222 | + Bear | |
223 | = Cat | |
224 | = Dog | |
225 | = Bear | |
226 | ||
227 | Reactors can keep state. | |
228 | ||
229 | | (define inc (macro (self args env) | |
230 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
231 | | (reactor (line-terminal) 65 | |
232 | | (macro (self args env) | |
233 | | (bind state (head (tail args)) | |
234 | | (bind event (head args) | |
235 | | (bind event-type (head event) | |
236 | | (bind event-payload (head (tail event)) | |
237 | | (if (equal? event-type (literal readln)) | |
238 | | (list (inc state) (list (literal writeln) (list state))) | |
239 | | (list state)))))))) | |
240 | + Cat | |
241 | + Dog | |
242 | + Giraffe | |
243 | = A | |
244 | = B | |
245 | = C | |
246 | ||
247 | Multiple reactors can be instantiated, will react to the same events. | |
248 | Note that reactors react in the *opposite* order they were installed. | |
249 | ||
250 | | (define inc (macro (self args env) | |
251 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
252 | | (reactor (line-terminal) 65 | |
253 | | (macro (self args env) | |
254 | | (bind state (head (tail args)) | |
255 | | (bind event (head args) | |
256 | | (bind event-type (head event) | |
257 | | (bind event-payload (head (tail event)) | |
258 | | (if (equal? event-type (literal readln)) | |
259 | | (list (inc state) (list (literal writeln) (list state))) | |
260 | | (list state)))))))) | |
261 | | (reactor (line-terminal) 0 | |
262 | | (macro (self args env) | |
263 | | (bind event (head args) | |
264 | | (bind event-type (head event) | |
265 | | (bind event-payload (head (tail event)) | |
266 | | (if (equal? event-type (literal readln)) | |
267 | | (list 0 | |
268 | | (list (literal writeln) event-payload)) | |
269 | | (list 0))))))) | |
270 | + Cat | |
271 | + Dog | |
272 | + Giraffe | |
273 | = Cat | |
274 | = A | |
275 | = Dog | |
276 | = B | |
277 | = Giraffe | |
278 | = C | |
279 | ||
280 | A reactor can stop by issuing a `stop` command. | |
281 | ||
282 | | (define inc (macro (self args env) | |
283 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
284 | | (reactor (line-terminal) 65 | |
285 | | (macro (self args env) | |
286 | | (bind state (head (tail args)) | |
287 | | (bind event (head args) | |
288 | | (bind event-type (head event) | |
289 | | (bind event-payload (head (tail event)) | |
290 | | (if (equal? event-type (literal readln)) | |
291 | | (if (equal? state 68) | |
292 | | (list state (list (literal stop) 0)) | |
293 | | (list (inc state) (list (literal writeln) event-payload))) | |
294 | | (list state)))))))) | |
295 | + Cat | |
296 | + Dog | |
297 | + Giraffe | |
298 | + Penguin | |
299 | + Alligator | |
300 | = Cat | |
301 | = Dog | |
302 | = Giraffe | |
303 | ||
304 | Stopping one reactor does not stop others. | |
305 | ||
306 | | (define inc (macro (self args env) | |
307 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
308 | | (reactor (line-terminal) 65 | |
309 | | (macro (self args env) | |
310 | | (bind state (head (tail args)) | |
311 | | (bind event (head args) | |
312 | | (bind event-type (head event) | |
313 | | (bind event-payload (head (tail event)) | |
314 | | (if (equal? event-type (literal readln)) | |
315 | | (if (equal? state 68) | |
316 | | (list state (list (literal stop) 0)) | |
317 | | (list (inc state) (list (literal writeln) event-payload))) | |
318 | | (list state)))))))) | |
319 | | (reactor (line-terminal) 65 | |
320 | | (macro (self args env) | |
321 | | (bind state (head (tail args)) | |
322 | | (bind event (head args) | |
323 | | (bind event-type (head event) | |
324 | | (bind event-payload (head (tail event)) | |
325 | | (if (equal? event-type (literal readln)) | |
326 | | (list (inc state) (list (literal writeln) (list state))) | |
327 | | (list state)))))))) | |
328 | + Cat | |
329 | + Dog | |
330 | + Giraffe | |
331 | + Penguin | |
332 | + Alligator | |
333 | = A | |
334 | = Cat | |
335 | = B | |
336 | = Dog | |
337 | = C | |
338 | = Giraffe | |
339 | = D | |
340 | = E | |
341 | ||
342 | Subscribing and unsubscribing to facilities | |
343 | ------------------------------------------- | |
344 | ||
345 | TODO. | |
346 | ||
347 | Listing a facility in the SUBSCRIPTIONS of the reactor isn't the | |
348 | only way for a reactor to be notified of events from the facility. | |
349 | The reactor can also subscribe to the facility at some point after the | |
350 | reactor has started, and later even unsubscribe from it as well. | |
351 | ||
352 | Communicating between reactors | |
353 | ------------------------------ | |
354 | ||
355 | It is not really recommended to implement a system with multiple | |
356 | reactors. It is better to compose a single large reactor out of | |
357 | multiple macros. | |
358 | ||
359 | But currently we allow it, so we should say some words about it. | |
360 | ||
361 | When a reactor issues a command, all other reactors see it as an | |
362 | event. |
91 | 91 | ### `reactor` ### |
92 | 92 | |
93 | 93 | `(reactor LIST-OF-ATOMS STATE-EXPR BODY-EXPR)` installs a reactor. Reactors |
94 | permit the construction of interactive Robin programs. See the document | |
95 | [Reactor.md](Reactor.md) for more information on, examples of, and tests for reactors. | |
94 | permit the construction of reactive Robin programs. See the | |
95 | [Reactors](#reactors) section for more information on reactors. | |
96 | 96 | |
97 | 97 | Intrinsic Data Types |
98 | 98 | -------------------- |
503 | 503 | | ;''This program, it produces a list of two booleans. #k ?'' |
504 | 504 | | (prepend #f (prepend #f ()))) |
505 | 505 | = (#f #f) |
506 | ||
507 | Reactors | |
508 | -------- | |
509 | ||
510 | To separate the concerns of computation and interaction, Robin provides | |
511 | a construct called a _reactor_. While evaluation of a Robin expression | |
512 | accomplishes side-effect-free computation, reactors permit the construction | |
513 | of so-called "reactive" programs, which are driven by events and may | |
514 | interact with a user, a remote server on a network, or other source of | |
515 | events. Reactors are similar to event handlers in Javascript, or to | |
516 | processes in Erlang. | |
517 | ||
518 | In Robin 0.3, a reactor is installed by giving a top-level form with the | |
519 | following syntax: | |
520 | ||
521 | (reactor SUBSCRIPTIONS INITIAL-STATE-EXPR TRANSDUCER) | |
522 | ||
523 | The first argument of the `reactor` form is a literal (unevaluated) list | |
524 | of symbols, called the _subscriptions_ for the reactor. Each symbol names | |
525 | a _facility_ with which the reactor wishes to be able to interact. | |
526 | ||
527 | The second argument is evaluated, and becomes the _initial state_ of the | |
528 | reactor. | |
529 | ||
530 | The third argument of the `reactor` form is evaluated to obtain a | |
531 | macro. This is called the _transducer_ of the reactor. | |
532 | ||
533 | Whenever an event of interest to the reactor occurs, the transducer is | |
534 | evaluated, being passed two (pre-evaluated) arguments: | |
535 | ||
536 | * A two-element list called the _event_. The elements are: | |
537 | * A literal symbol called the _event type_, specifying what kind of event | |
538 | happened. | |
539 | * An arbitrary value called the _event payload_ containing more data about | |
540 | the event, in a format specific to the type of event. | |
541 | * The current state of the reactor. (This will be the initial state | |
542 | if the reactor body has never before been evaluated.) | |
543 | ||
544 | Given these things, the transducer is expected to evaluate to a list where the | |
545 | first element is the new state of the reactor, and each of the subsequent | |
546 | elements is an _command_, which is itself a two-element list containing: | |
547 | ||
548 | * A literal symbol called the _command type_ specifying the kind of | |
549 | command that is being requested to be executed; and | |
550 | * An arbitrary value called the _command payload_ containing more data | |
551 | about the command, in a format specific to that type of command. | |
552 | ||
553 | There may of course be zero commands in the returned list, but it is an | |
554 | error if the returned value is not a list containing at least one element. | |
555 | ||
556 | If the transducer throws an error, no commands will be executed, and | |
557 | the state of the transducer will remain unchanged. Implementations | |
558 | should allow such occurrences to be visible and/or logged. | |
559 | ||
560 | In fact, commands _are_ events. We just call them commands when it is | |
561 | a reactor producing them, and events when a reactor is receiving them. | |
562 | ||
563 | Standard Events | |
564 | --------------- | |
565 | ||
566 | ### `init` ### | |
567 | ||
568 | When a reactor first starts up it will receive an event telling it that | |
569 | it has started up. The event type for this event is the literal symbol | |
570 | `init`. | |
571 | ||
572 | There are two things a reactor will almost always want to do when it | |
573 | receives an `init` event: establish a known initial state, and | |
574 | subscribe to facilities. | |
575 | ||
576 | It establishes a known initial state by returning an initial state | |
577 | value as the first element of the list it returns. | |
578 | ||
579 | It subscribes to facilities by returning commands which request | |
580 | subscription to those facilities. Once subscribed, it will receive | |
581 | `subscribed` events from them. If the facility is not available, it | |
582 | will receive a `not-available` event. It may then elect to abort, | |
583 | or choose an alternate facility, or so forth. | |
584 | ||
585 | Standard Commands | |
586 | ----------------- | |
587 | ||
588 | ### `stop` ### | |
589 | ||
590 | Stops the current reactor, and removes it from the list of active | |
591 | reactors. It will no longer receive any events. | |
592 | ||
593 | Standard Facilities | |
594 | ------------------- | |
595 | ||
596 | If a reactor isn't subscribed to any facilities, it won't necessarily | |
597 | receive any events, although this is implementation-specific. | |
598 | ||
599 | The set of facilities is expected to be largely implementation-specific. | |
600 | ||
601 | All the same, there will probably be a set of standard facilities. | |
602 | ||
603 | Let's describe one such facility for concreteness. | |
604 | ||
605 | ### `line-terminal` ### | |
606 | ||
607 | -> Tests for functionality "Interpret Robin Program (with Small)" | |
608 | ||
609 | The `line-terminal` facility allows a Robin program to interact with | |
610 | something or someone over a line-oriented protocol, similar to what | |
611 | "standard I/O" routed to a terminal gets you under Unix. Note that | |
612 | this is not guaranteed to be the "real" standard I/O; it could well be | |
613 | simulated with modal dialogue boxes in a GUI, or with textareas on a web | |
614 | page under Javascript, or so forth. | |
615 | ||
616 | The `line-terminal` facility understands commands of the form | |
617 | ||
618 | (writeln <STRING>) | |
619 | ||
620 | The `<STRING>` argument should be a Robin string (list of integers). Those | |
621 | integers, as bytes, are sent to whetever is listening on the other end of | |
622 | the line terminal. When attached to an actual terminal console (whether real | |
623 | or emulated), this would typically cause an ASCII representation of those bytes | |
624 | to be displayed. | |
625 | ||
626 | Knowing this, we can write a "Hello, world!" program. To keep it | |
627 | simple, we'll simply assume the line-terminal facility exists. | |
628 | We won't bother to subscribe to it. In addition, note that this reactor | |
629 | essentially doesn't keep any state — the initial state of the reactor is simply | |
630 | the integer 0, and the state is set to 0 after each event is reacted to. | |
631 | ||
632 | | (reactor (line-terminal) 0 | |
633 | | (macro (self args env) | |
634 | | (bind event (head args) | |
635 | | (bind event-type (head event) | |
636 | | (if (equal? event-type (literal init)) | |
637 | | (list 0 | |
638 | | (list (literal writeln) (literal ''Hello, world!'')) | |
639 | | (list (literal stop) 0)) | |
640 | | (list 0)))))) | |
641 | = Hello, world! | |
642 | ||
643 | Reactors which interact with `line-terminal` receive `readln` events. | |
644 | ||
645 | `readln`, is sent when a line of text is received | |
646 | on the "standard input". The payload for this event is a Robin string | |
647 | of the line of text received. This string does not contain any end-of-line | |
648 | marker characters. | |
649 | ||
650 | Thus we can construct a simple `cat` program: | |
651 | ||
652 | | (reactor (line-terminal) 0 | |
653 | | (macro (self args env) | |
654 | | (bind event (head args) | |
655 | | (bind event-type (head event) | |
656 | | (bind event-payload (head (tail event)) | |
657 | | (if (equal? event-type (literal readln)) | |
658 | | (list 0 | |
659 | | (list (literal writeln) event-payload)) | |
660 | | (list 0))))))) | |
661 | + Cat | |
662 | + Dog | |
663 | = Cat | |
664 | = Dog | |
665 | ||
666 | General Reactor properties | |
667 | -------------------------- | |
668 | ||
669 | A reactor can issue multiple commands in its response to an event. | |
670 | ||
671 | | (reactor (line-terminal) 0 | |
672 | | (macro (self args env) | |
673 | | (bind event (head args) | |
674 | | (bind event-type (head event) | |
675 | | (bind event-payload (head (tail event)) | |
676 | | (if (equal? event-type (literal readln)) | |
677 | | (list 0 | |
678 | | (list (literal writeln) (literal ''Line:'')) | |
679 | | (list (literal writeln) event-payload)) | |
680 | | (list 0))))))) | |
681 | + Cat | |
682 | + Dog | |
683 | = Line: | |
684 | = Cat | |
685 | = Line: | |
686 | = Dog | |
687 | ||
688 | When receiving a malformed command, a facility may produce a warning | |
689 | message of some kind, but it should otherwise ignore it and keep going. | |
690 | ||
691 | | (reactor (line-terminal) 0 | |
692 | | (macro (self args env) | |
693 | | (bind event (head args) | |
694 | | (bind event-type (head event) | |
695 | | (bind event-payload (head (tail event)) | |
696 | | (if (equal? event-type (literal readln)) | |
697 | | (list 0 | |
698 | | (literal what-is-this) | |
699 | | (literal i-dont-even) | |
700 | | (list (literal writeln) event-payload)) | |
701 | | (list 0))))))) | |
702 | + Cat | |
703 | + Dog | |
704 | = Cat | |
705 | = Dog | |
706 | ||
707 | If evaluating the transducer of a reactor raises an error, the reactor | |
708 | remains in the same state and issues no commands, but always recovers | |
709 | so that it can continue to handle subsequent events (i.e. it does not crash). | |
710 | ||
711 | An implementation is encouraged to allow these to be logged (and the | |
712 | reference implementation will display them if `--show-events` is given) | |
713 | but this is not a strict requirement. | |
714 | ||
715 | | (reactor (line-terminal) 0 | |
716 | | (macro (self args env) | |
717 | | (bind event (head args) | |
718 | | (bind event-type (head event) | |
719 | | (bind event-payload (head (tail event)) | |
720 | | (if (equal? event-type (literal readln)) | |
721 | | (if (equal? (head event-payload) 65) | |
722 | | (raise 999999) | |
723 | | (list 0 (list (literal writeln) event-payload))) | |
724 | | (list 0))))))) | |
725 | + Cat | |
726 | + Dog | |
727 | + Alligator | |
728 | + Bear | |
729 | = Cat | |
730 | = Dog | |
731 | = Bear | |
732 | ||
733 | Reactors can keep state. | |
734 | ||
735 | | (define inc (macro (self args env) | |
736 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
737 | | (reactor (line-terminal) 65 | |
738 | | (macro (self args env) | |
739 | | (bind state (head (tail args)) | |
740 | | (bind event (head args) | |
741 | | (bind event-type (head event) | |
742 | | (bind event-payload (head (tail event)) | |
743 | | (if (equal? event-type (literal readln)) | |
744 | | (list (inc state) (list (literal writeln) (list state))) | |
745 | | (list state)))))))) | |
746 | + Cat | |
747 | + Dog | |
748 | + Giraffe | |
749 | = A | |
750 | = B | |
751 | = C | |
752 | ||
753 | Multiple reactors can be instantiated, will react to the same events. | |
754 | Note that reactors react in the *opposite* order they were installed. | |
755 | ||
756 | | (define inc (macro (self args env) | |
757 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
758 | | (reactor (line-terminal) 65 | |
759 | | (macro (self args env) | |
760 | | (bind state (head (tail args)) | |
761 | | (bind event (head args) | |
762 | | (bind event-type (head event) | |
763 | | (bind event-payload (head (tail event)) | |
764 | | (if (equal? event-type (literal readln)) | |
765 | | (list (inc state) (list (literal writeln) (list state))) | |
766 | | (list state)))))))) | |
767 | | (reactor (line-terminal) 0 | |
768 | | (macro (self args env) | |
769 | | (bind event (head args) | |
770 | | (bind event-type (head event) | |
771 | | (bind event-payload (head (tail event)) | |
772 | | (if (equal? event-type (literal readln)) | |
773 | | (list 0 | |
774 | | (list (literal writeln) event-payload)) | |
775 | | (list 0))))))) | |
776 | + Cat | |
777 | + Dog | |
778 | + Giraffe | |
779 | = Cat | |
780 | = A | |
781 | = Dog | |
782 | = B | |
783 | = Giraffe | |
784 | = C | |
785 | ||
786 | A reactor can stop by issuing a `stop` command. | |
787 | ||
788 | | (define inc (macro (self args env) | |
789 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
790 | | (reactor (line-terminal) 65 | |
791 | | (macro (self args env) | |
792 | | (bind state (head (tail args)) | |
793 | | (bind event (head args) | |
794 | | (bind event-type (head event) | |
795 | | (bind event-payload (head (tail event)) | |
796 | | (if (equal? event-type (literal readln)) | |
797 | | (if (equal? state 68) | |
798 | | (list state (list (literal stop) 0)) | |
799 | | (list (inc state) (list (literal writeln) event-payload))) | |
800 | | (list state)))))))) | |
801 | + Cat | |
802 | + Dog | |
803 | + Giraffe | |
804 | + Penguin | |
805 | + Alligator | |
806 | = Cat | |
807 | = Dog | |
808 | = Giraffe | |
809 | ||
810 | Stopping one reactor does not stop others. | |
811 | ||
812 | | (define inc (macro (self args env) | |
813 | | (subtract (eval env (head args)) (subtract 0 1)))) | |
814 | | (reactor (line-terminal) 65 | |
815 | | (macro (self args env) | |
816 | | (bind state (head (tail args)) | |
817 | | (bind event (head args) | |
818 | | (bind event-type (head event) | |
819 | | (bind event-payload (head (tail event)) | |
820 | | (if (equal? event-type (literal readln)) | |
821 | | (if (equal? state 68) | |
822 | | (list state (list (literal stop) 0)) | |
823 | | (list (inc state) (list (literal writeln) event-payload))) | |
824 | | (list state)))))))) | |
825 | | (reactor (line-terminal) 65 | |
826 | | (macro (self args env) | |
827 | | (bind state (head (tail args)) | |
828 | | (bind event (head args) | |
829 | | (bind event-type (head event) | |
830 | | (bind event-payload (head (tail event)) | |
831 | | (if (equal? event-type (literal readln)) | |
832 | | (list (inc state) (list (literal writeln) (list state))) | |
833 | | (list state)))))))) | |
834 | + Cat | |
835 | + Dog | |
836 | + Giraffe | |
837 | + Penguin | |
838 | + Alligator | |
839 | = A | |
840 | = Cat | |
841 | = B | |
842 | = Dog | |
843 | = C | |
844 | = Giraffe | |
845 | = D | |
846 | = E | |
847 | ||
848 | ### Subscribing and unsubscribing to facilities ### | |
849 | ||
850 | TODO. | |
851 | ||
852 | Listing a facility in the SUBSCRIPTIONS of the reactor isn't the | |
853 | only way for a reactor to be notified of events from the facility. | |
854 | The reactor can also subscribe to the facility at some point after the | |
855 | reactor has started, and later even unsubscribe from it as well. | |
856 | ||
857 | ### Communicating between reactors ### | |
858 | ||
859 | It is not really recommended to implement a system with multiple | |
860 | reactors. It is better to compose a single large reactor out of | |
861 | multiple macros. | |
862 | ||
863 | But currently we allow it, so we should say some words about it. | |
864 | ||
865 | When a reactor issues a command, all other reactors see it as an | |
866 | event. |
5 | 5 | APPLIANCES="appliances/robin.md appliances/robin-no-builtins.md" |
6 | 6 | fi |
7 | 7 | |
8 | TESTDOCS=" | |
9 | doc/Robin.md | |
10 | doc/Reactor.md | |
11 | " | |
12 | ||
13 | 8 | echo "Running tests on core semantics..." |
14 | falderal -b $APPLIANCES $TESTDOCS || exit 1 | |
9 | falderal -b $APPLIANCES doc/Robin.md || exit 1 | |
15 | 10 | |
16 | 11 | for PACKAGE in intrinsics small boolean arith list env misc; do |
17 | 12 | echo "Running tests on '$PACKAGE' package..." |