1 | # jssm
|
2 |
|
3 | Wouldn't it be nice if your Javascript state machines were simple and readable?
|
4 |
|
5 | ```javascript
|
6 | import { sm } from 'jssm';
|
7 |
|
8 | const Machine = sm`
|
9 | Green => Yellow => Red => Green;
|
10 | [Red Yellow Green] ~> Off -> Red;
|
11 | `;
|
12 |
|
13 | console.log( Machine.state() ); // 'Green'
|
14 |
|
15 | Machine.transition('Yellow'); // true
|
16 | console.log( Machine.state() ); // 'Yellow'
|
17 |
|
18 | Machine.transition('Blue'); // false
|
19 | console.log( Machine.state() ); // 'Yellow'
|
20 | ```
|
21 |
|
22 | What if that were easy to render visually, with styling?
|
23 |
|
24 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/doc%20light%20styled.png)
|
25 |
|
26 |
|
27 |
|
28 | <br/><br/>
|
29 |
|
30 | ## Introducing JSSM
|
31 |
|
32 | JSSM is a Javascript state machine implementing [Finite State Language](https://fsl.tools/), with a terse DSL and a simple API.
|
33 | 100% test coverage; typed with Flowtype. MIT licensed.
|
34 |
|
35 | The NPM package includes pure `es6`, a `cjs es5` bundle, and `.d.ts` typings. The repository includes the original typescript, the bundle, the es6, documentation, tests, tutorials, and so on.
|
36 |
|
37 | [Try it live!](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html)
|
38 |
|
39 | Visualize with [jssm-viz](https://github.com/StoneCypher/jssm-viz), or at the command line with [jssm-viz-cli](https://github.com/StoneCypher/jssm-viz-cli).
|
40 |
|
41 | Language test cases for Belorussian, English, German, Hebrew, Italian, Russian, Spanish, Ukrainian, and Emoji. Please help to make sure that your language is well handled!
|
42 |
|
43 | <div id="badge_style_hook">
|
44 |
|
45 | [![Actions Status](https://github.com/StoneCypher/jssm/workflows/Node%20CI/badge.svg)](https://github.com/StoneCypher/jssm/actions)
|
46 |
|
47 | [![GitHub forks](https://img.shields.io/github/forks/StoneCypher/jssm.svg?style=social&label=Fork%20JSSM)]()
|
48 | [![GitHub watchers](https://img.shields.io/github/watchers/StoneCypher/jssm.svg?style=social&label=Watch%20JSSM)]()
|
49 | [![GitHub stars](https://img.shields.io/github/stars/StoneCypher/jssm.svg?style=social&label=JSSM%20Stars)]()
|
50 |
|
51 | [![GitHub followers](https://img.shields.io/github/followers/StoneCypher.svg?style=social&label=Follow%20StoneCypher)]()
|
52 |
|
53 | [![License](https://img.shields.io/npm/l/jssm.svg)](https://github.com/StoneCypher/jssm/blob/master/LICENSE.md)
|
54 | [![Open issues](https://img.shields.io/github/issues/StoneCypher/jssm.svg)](https://github.com/StoneCypher/jssm/issues)
|
55 | [![Closed issues](https://img.shields.io/github/issues-closed/StoneCypher/jssm.svg)](https://github.com/StoneCypher/jssm/issues?q=is%3Aissue+is%3Aclosed)
|
56 |
|
57 | [![Dependency status](https://david-dm.org/StoneCypher/jssm/status.svg)](https://david-dm.org/StoneCypher/jssm)
|
58 | [![Travis status](https://img.shields.io/travis/StoneCypher/jssm.svg)](https://travis-ci.org/StoneCypher/jssm)
|
59 | [![Coveralls status](https://img.shields.io/coveralls/StoneCypher/jssm.svg)](https://coveralls.io/github/StoneCypher/jssm)
|
60 |
|
61 | [![NPM version](https://img.shields.io/npm/v/jssm.svg)](https://www.npmjs.com/package/jssm)
|
62 | [![CDNjs version](https://img.shields.io/cdnjs/v/jquery.svg)](https://img.shields.io/cdnjs/v/jquery.svg)
|
63 | [![NPM downloads](https://img.shields.io/npm/dt/jssm.svg)](https://www.npmjs.com/package/jssm)
|
64 |
|
65 | </div>
|
66 |
|
67 |
|
68 |
|
69 | <br/><br/>
|
70 |
|
71 | ## TL;DR
|
72 | Specify finite state machines with a brief syntax. Run them; they're fast. Make mistakes; they're strict. Derive
|
73 | charts. Save and load states, and histories. Make machine factories to churn out dozens or thousands of instances.
|
74 | Impress friends and loved ones. Cure corns and callouses.
|
75 |
|
76 | ```fsl
|
77 | Red 'Proceed' -> Green 'Proceed' -> Yellow 'Proceed' -> Red;
|
78 | ```
|
79 |
|
80 | This will produce the following FSM (graphed with [jssm-viz](https://github.com/StoneCypher/jssm-viz)):
|
81 |
|
82 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/ryg%20proceed.png)
|
83 |
|
84 | You'll build an executable state machine.
|
85 |
|
86 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/ryg%20traffic%20light%20console%20screenshot.png)
|
87 |
|
88 |
|
89 |
|
90 | <br/><br/>
|
91 |
|
92 | ## Why
|
93 |
|
94 | As usual, a valid question.
|
95 |
|
96 |
|
97 |
|
98 | <br/>
|
99 |
|
100 | ### Why state machines
|
101 |
|
102 | State machines are a method of making your software better able to prevent illegal states. Similar to type systems, SQL
|
103 | constraints, and linters, state machines are a way to teach the software to catch mistakes in ways you define, to help
|
104 | lead to better software.
|
105 |
|
106 | The major mechanism of a state machine is to define `states`, the `transitions` between them, and sometimes associated
|
107 | `data` and other niceties. The minor mechanism of state machines is to attach `actions` to the transitions, such that
|
108 | the state machine can partially run itself.
|
109 |
|
110 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/ryg%20proceed.png)
|
111 |
|
112 | So, to look at the same traffic light as above, you'll notice some things.
|
113 |
|
114 | 1. A sufficiently smart implementation will know that it's okay for `Green` to switch to `Yellow`, but not to `Red`
|
115 | 1. A sufficiently smart implementation knows there's no such thing as `Blue`
|
116 | 1. A sufficiently smart implementation knows that when in `Green`, to be told to `Proceed` means to go to `Yellow`, but
|
117 | when in `Yellow`, it means to go to `Red` instead
|
118 |
|
119 | Along with other common sense things, a good state machine implementation can help eliminate large classes of error in
|
120 | software. State machines are often applied when the stakes on having things correct are high.
|
121 |
|
122 |
|
123 |
|
124 | <br/>
|
125 |
|
126 | ### Why this implementation
|
127 |
|
128 | Brevity.
|
129 |
|
130 | High quality testing. JSSM has 100% coverage, and has partial stochastic test coverage.
|
131 |
|
132 | Feature parity, especially around the DSL and data control.
|
133 |
|
134 | Data integrity. JSSM allows a much stricter form of state machine than is common, with a relatively low performance
|
135 | and storage overhead. It also offers an extremely terse domain specific language (though it does not require said DSL)
|
136 | to produce state machines in otherwise comparatively tiny and easily read code.
|
137 |
|
138 |
|
139 |
|
140 | <br/><br/>
|
141 |
|
142 | ## Quick Start
|
143 |
|
144 | > A state machine in `JSSM` is defined in one of two ways: through the DSL, or through a datastructure.
|
145 |
|
146 | So yeah, let's start by getting some terminology out of the way, and then we can go right back to that impenetrable
|
147 | sentence, so that it'll make sense.
|
148 |
|
149 |
|
150 |
|
151 | <br/>
|
152 |
|
153 | ### Quick Terminology
|
154 |
|
155 | Finite state machines have been around forever, are used by everyone, and are hugely important. As a result, the
|
156 | terminology is a mess, is in conflict, and is very poorly chosen, in accordince with everything-is-horrible law.
|
157 |
|
158 | This section describes the terminology *as used by this library*. The author has done his best to choose a terminology
|
159 | that matches common use and will be familiar to most. Conflicts are explained in the following section, to keep this
|
160 | simple.
|
161 |
|
162 | For this quick overview, we'll define six basic concepts:
|
163 |
|
164 | 1. `Finite state machine`s
|
165 | 1. `Machine`s
|
166 | 1. `State`s
|
167 | 1. `Current state`
|
168 | 1. `Transition`s
|
169 | 1. `Action`s
|
170 |
|
171 | There's other stuff, of course, but these five are enough to wrap your head around `finite state machine`s.
|
172 |
|
173 |
|
174 |
|
175 | <br/>
|
176 |
|
177 | #### Basic concepts
|
178 |
|
179 | This is a trivial traffic light `FSM`, with three states, three transitions, and one action:
|
180 |
|
181 | ```fsl
|
182 | Red 'Proceed' -> Green 'Proceed' -> Yellow 'Proceed' -> Red;
|
183 | ```
|
184 |
|
185 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/ryg%20proceed.png)
|
186 |
|
187 | Let's review its pieces.
|
188 |
|
189 | * `finite state machine`s
|
190 | * A `finite state machine` (or `FSM`) is a collection of `state`s, and rules about how you can `transition` between
|
191 | the `state`s.
|
192 | * We tend to refer to a design for a machine as "an `FSM`."
|
193 | * In this example, the traffic light's structure is "a traffic light `FSM`."
|
194 |
|
195 | * `state`s
|
196 | * `FSM`s always have at least one `state`, and nearly always many `state`s
|
197 | * In this example,
|
198 | * the `state`s are **Red**, **Yellow**, and **Green**
|
199 | * Something made from this `FSM` will only ever be one of those colors - not, say, **Blue**
|
200 |
|
201 | * `machine`s
|
202 | * Single instances of an `FSM` are referred to as a `machine`
|
203 | * We might have a thousand instances of the traffic light designed above
|
204 | * We would say "My intersection has four `machines` of the standard three color light `FSM`."
|
205 |
|
206 | * `current state`
|
207 | * A `machine` has a `current state`, though an `FSM` does not
|
208 | * "This specific traffic light is currently **Red**"
|
209 | * Traffic lights in general do not have a current color, only specific lights
|
210 | * `FSM`s do not have a current state, only specific `machine`s
|
211 | * A given `machine` will always have exactly one `state` - never multiple, never none
|
212 |
|
213 | * `transitions`
|
214 | * `FSM`s nearly always have `transition`s
|
215 | * Transitions govern whether a `state` may be reached from another `state`
|
216 | * This restriction is much of the value of `FSM`s
|
217 | * In this example,
|
218 | * the `transition`s are
|
219 | * **Green** → **Yellow**
|
220 | * **Yellow** → **Red**
|
221 | * **Red** → **Green**
|
222 | * a `machine` whose `current state` is **Green** may switch to **Yellow**, because there is an appropriate transition
|
223 | * a `machine` whose `current state` is **Green** may not switch to **Red**, or to **Green** anew, because there is no
|
224 | such transition
|
225 | * A `machine` in **Yellow** which is told to `transition` to **Green** (which isn't legal) will know to refuse
|
226 | * This makes `FSM`s an effective tool for error prevention
|
227 |
|
228 | * `actions`
|
229 | * Many `FSM`s have `action`s, which represent events from the outside world.
|
230 | * In this example, there is only one action - **Proceed**
|
231 | * The `action` **Proceed** is available from all three colors
|
232 | * At any time we may indicate to this light to go to its next color, without
|
233 | taking the time to know what it is.
|
234 | * This allows `FSM`s like the light to self-manage.
|
235 | * A `machine` in **Yellow** which is told to take the `action` **Proceed** will
|
236 | know on its own to switch its `current state` to **Red**.
|
237 | * This makes `FSM`s an effective tool for complexity reduction
|
238 |
|
239 | Those six ideas in hand - `FSM`s, `state`s, `machine`s, `current state`, `transition`s, and `action`s - and you're ready
|
240 | to move forwards.
|
241 |
|
242 | One other quick definition - a `DSL`, or `domain specific language`, is when someone makes a language and embeds it into
|
243 | a different language, for the purpose of attacking a specific job. When `React` uses a precompiler to embed stuff that
|
244 | looks like HTML in Javascript, that's a DSL.
|
245 |
|
246 | This library implements a simple language for `defining finite state machine`s inside of strings. For example, this
|
247 | `DSL` defines that `'a -> b;'` actually means "create two states, create a transition between them, assign the first as
|
248 | the initial state", et cetera. That micro-language is the `DSL` that we'll be referring to a lot, coming up. This
|
249 | `DSL`'s formal name is `jssm-dot`, because it's a descendant-in-spirit of an older flowcharting language
|
250 | [DOT](http://www.graphviz.org/content/dot-language), from [graphviz](graphviz.org), which is also used to make the
|
251 | visualizations in [jssm-viz](https://github.com/StoneCypher/jssm-viz) by way of [viz-js](viz-js.com).
|
252 |
|
253 | Enough history lesson. On with the tooling.
|
254 |
|
255 |
|
256 |
|
257 | <br/>
|
258 |
|
259 | ### And now, that Quick Start we were talking about
|
260 |
|
261 | So let's put together a trivial four-state traffic light: the three colors, plus **Off**. This will give us an
|
262 | opportunity to go over the basic facilities in the language.
|
263 |
|
264 | At any time, you can take the code and put it into the
|
265 | [graph explorer](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) for an opportunity to mess with the
|
266 | code as you see fit.
|
267 |
|
268 |
|
269 |
|
270 | <br/>
|
271 |
|
272 | #### 0: Lights always have an off state
|
273 |
|
274 | Our light will start in the **Off** `state`, with the ability to switch to the **Red** `state`.
|
275 |
|
276 | Since that's a normal, not-notable thing, we'll just make it a regular `-> legal transition`.
|
277 |
|
278 | ```fsl
|
279 | Off -> Red;
|
280 | ```
|
281 |
|
282 | We will give that `transition` an `action`, and call it **TurnOn**.
|
283 |
|
284 | ```fsl
|
285 | Off 'TurnOn' -> Red;
|
286 | ```
|
287 |
|
288 | So far, our machine is simple:
|
289 |
|
290 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/traffic%20light%20quick%20start%20tutorial/Off%20To%20Red.png)
|
291 |
|
292 |
|
293 |
|
294 | <br/>
|
295 |
|
296 | #### 1: Traffic lights have a three-color cycle
|
297 |
|
298 | The main path of a traffic light is cycling from **Green** to **Yellow**, then to **Red**, then back again. Because
|
299 | this is the main path, we'll mark these steps `=> main transition`s.
|
300 |
|
301 | ```fsl
|
302 | Off 'TurnOn' -> Red => Green => Yellow => Red;
|
303 | ```
|
304 |
|
305 | We will give those all the same action name, **Proceed**, indicating "next color" without needing to know what we're
|
306 | currently on.
|
307 |
|
308 | ```fsl
|
309 | Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
|
310 | ```
|
311 |
|
312 | Machine's still pretty simple:
|
313 |
|
314 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/traffic%20light%20quick%20start%20tutorial/Off%20To%20RGY.png)
|
315 |
|
316 |
|
317 |
|
318 | <br/>
|
319 |
|
320 | #### 2: Traffic lights can be shut down
|
321 |
|
322 | We'd also like to be able to turn this light back off. Because that's expected to be a rarity, we'll require that it
|
323 | be a `~> forced transition`.
|
324 |
|
325 | We could write
|
326 |
|
327 | ```fsl
|
328 | Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
|
329 | Red ~> Off;
|
330 | Yellow ~> Off;
|
331 | Green ~> Off;
|
332 | ```
|
333 |
|
334 | But that takes a lot of space even with this short list, so, instead we'll use the array notation
|
335 |
|
336 | ```fsl
|
337 | Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
|
338 | [Red Yellow Green] ~> Off;
|
339 | ```
|
340 |
|
341 | And we'd like those all to have the action **TurnOff**, so
|
342 |
|
343 | ```fsl
|
344 | Off 'TurnOn' -> Red 'Proceed' => Green 'Proceed' => Yellow 'Proceed' => Red;
|
345 | [Red Yellow Green] 'TurnOff' ~> Off;
|
346 | ```
|
347 |
|
348 | Machine's still not too bad:
|
349 |
|
350 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/traffic%20light%20quick%20start%20tutorial/Off%20To%20From%20RGY.png)
|
351 |
|
352 |
|
353 |
|
354 | <br/>
|
355 |
|
356 | ### Let's actually use the traffic light
|
357 |
|
358 | That's actually the bulk of the language. There are other little add-ons here and there, but, primarily you now know
|
359 | how to write a state machine.
|
360 |
|
361 | Let's load it and use it! 😀
|
362 |
|
363 | #### loading into node
|
364 | #### loading into html
|
365 | #### jssm-viz
|
366 | #### redistribution on npm
|
367 |
|
368 |
|
369 |
|
370 | <br/>
|
371 |
|
372 | ### An introduction to machine design
|
373 |
|
374 | Let's make a `state machine` for ATMs. In the process, we will use a lot of core concepts of `finite state machine`s
|
375 | and of `jssm-dot`, this library's `DSL`.
|
376 |
|
377 | We're going to improve on this [NCSU ATM diagram](https://people.engr.ncsu.edu/efg/210/s99/Notes/fsm/atm.gif) that I
|
378 | found:
|
379 |
|
380 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/ncsu%20atm%20diagram.gif)
|
381 |
|
382 | Remember, at any time, you can take the code and put it into the
|
383 | [graph explorer](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) for an opportunity to mess with the
|
384 | code as you see fit.
|
385 |
|
386 |
|
387 |
|
388 | <br/>
|
389 |
|
390 | #### 0: Empty machine
|
391 |
|
392 | We'll start with an [empty machine](https://github.com/StoneCypher/jssm/blob/master/src/machines/atm%20quick%20start%20tutorial/1_EmptyWaiting.jssm).
|
393 |
|
394 | ```fsl
|
395 | EmptyWaiting 'Wait' -> EmptyWaiting;
|
396 | ```
|
397 |
|
398 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/0_EmptyWaiting.png)
|
399 |
|
400 |
|
401 |
|
402 | <br/>
|
403 |
|
404 | #### 1: Should be able to eject cards
|
405 |
|
406 | We'll add the ability to physically eject the user's card and reset to the empty and waiting state. Right now it'll
|
407 | dangle around un-used at the top, but later it'll become useful.
|
408 |
|
409 | This is expressed as the path `EjectCardAndReset -> EmptyWaiting;`
|
410 |
|
411 | ```fsl
|
412 | EmptyWaiting 'Wait' -> EmptyWaiting;
|
413 | EjectCardAndReset -> EmptyWaiting;
|
414 | ```
|
415 |
|
416 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/1_EjectCard.png)
|
417 |
|
418 |
|
419 |
|
420 | <br/>
|
421 |
|
422 | #### 2: Should be able to insert cards
|
423 |
|
424 | We'll add the ability to physically insert a card, next. You know, the, uh, thing ATMs are pretty much for.
|
425 |
|
426 | To get this, add the path leg `EmptyWaiting 'InsertCard' -> HasCardNoAuth;`
|
427 |
|
428 | ```fsl
|
429 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
430 | EjectCardAndReset -> EmptyWaiting;
|
431 | ```
|
432 |
|
433 | Notice that the new `state`, **HasCardNoAuth**, has been rendered red. This is because it is `terminal` - there is
|
434 | no exit from this node currently. (**EmptyAndWaiting** did not render that way because it had a transition to itself.)
|
435 | That will change as we go back to adding more nodes. `terminal node`s are usually either mistakes or the last single
|
436 | `state` of a given `FSM`.
|
437 |
|
438 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/2_InsertCard.png)
|
439 |
|
440 |
|
441 |
|
442 | <br/>
|
443 |
|
444 | #### 3: Should be able to cancel and recover the card
|
445 |
|
446 | Next, we should have a cancel, because the ATM's <key>7</key> key is broken, and we need our card back. Cancel will
|
447 | exit to the main menu, and return our card credential.
|
448 |
|
449 | To that end, we add the path `HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;`
|
450 |
|
451 | ```fsl
|
452 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
453 |
|
454 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
455 |
|
456 | EjectCardAndReset -> EmptyWaiting;
|
457 | ```
|
458 |
|
459 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/3_ReturnCard.png)
|
460 |
|
461 |
|
462 |
|
463 | <br/>
|
464 |
|
465 | #### 4: Can give the wrong PIN
|
466 |
|
467 | Next, let's give the ability to get the password ... wrong. 😂 Because we all know that one ATM that only has the
|
468 | wrong-PIN path, so, apparently that's a product to someone.
|
469 |
|
470 | When they get the PIN wrong, they're prompted to try again (or to cancel.)
|
471 |
|
472 | We'll add the path `HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;`
|
473 |
|
474 | ```fsl
|
475 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
476 |
|
477 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
478 | HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
|
479 |
|
480 | EjectCardAndReset -> EmptyWaiting;
|
481 | ```
|
482 |
|
483 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/4_WrongPin.png)
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 | <br/>
|
490 |
|
491 | #### 5: Can give the correct PIN
|
492 |
|
493 | Next, let's give the ability to get the password right.
|
494 |
|
495 | We'll add two paths. The first gets the password right: `HasCardNoAuth 'RightPIN' -> MainMenu;`
|
496 |
|
497 | The second, from our new `state` **MainMenu**, gives people the ability to leave: `MainMenu 'ExitReturnCard' -> EjectCardAndReset;`
|
498 |
|
499 |
|
500 | ```fsl
|
501 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
502 |
|
503 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
504 | HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
|
505 | HasCardNoAuth 'RightPIN' -> MainMenu;
|
506 |
|
507 | MainMenu 'ExitReturnCard' -> EjectCardAndReset;
|
508 |
|
509 | EjectCardAndReset -> EmptyWaiting;
|
510 | ```
|
511 |
|
512 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/5_RightPin.png)
|
513 |
|
514 |
|
515 |
|
516 | <br/>
|
517 |
|
518 | #### 6: Can check balance from main menu
|
519 |
|
520 | Hooray, now we're getting somewhere.
|
521 |
|
522 | Let's add the ability to check your balance. First pick that from the main menu, then pick which account to see the
|
523 | balance of, then you're shown a screen with the information you requested; then go back to the main menu.
|
524 |
|
525 | That's `MainMenu 'CheckBalance' -> PickAccount -> DisplayBalance -> MainMenu;`.
|
526 |
|
527 | ```fsl
|
528 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
529 |
|
530 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
531 | HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
|
532 | HasCardNoAuth 'RightPIN' -> MainMenu;
|
533 |
|
534 | MainMenu 'ExitReturnCard' -> EjectCardAndReset;
|
535 | MainMenu 'CheckBalance' -> PickAccount -> DisplayBalance -> MainMenu;
|
536 |
|
537 | EjectCardAndReset -> EmptyWaiting;
|
538 | ```
|
539 |
|
540 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/6_CanCheckBalance.png)
|
541 |
|
542 |
|
543 |
|
544 | <br/>
|
545 |
|
546 | #### 7: Can deposit money from main menu
|
547 |
|
548 | Let's add something difficult. Their state machine just proceeds assuming everything is okay.
|
549 |
|
550 | To desposit money:
|
551 |
|
552 | 1. Accept physical money
|
553 | 2. If accept failed (eg door jammed,) reject physical object, go to main menu
|
554 | 3. If accept succeeded, ask human expected value
|
555 | 4. Pick an account this should go into
|
556 | 5. Contact bank. Request to credit for theoretical physical money.
|
557 | 6. Three results: yes, no, offer-after-audit.
|
558 | 7. If no, reject physical object, go to main menu.
|
559 | 8. If yes, consume physical object, tell user consumed, go to main menu
|
560 | 9. If offer-after-audit, ask human what to do
|
561 | 10. if human-yes, consume physical object, tell user consumed, go to main menu
|
562 | 11. if human-no, reject physical object, go to main menu
|
563 |
|
564 | Writing this out in code is not only generally longer than the text form, but also error prone and hard to maintain.
|
565 |
|
566 | ... or there's the `FSM` `DSL`, which is usually as-brief-as the text, and frequently both briefer and more explicit.
|
567 |
|
568 | * Rules 1-2: `MainMenu 'AcceptDeposit' -> TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;`
|
569 | * Rules 3-6: `TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;`
|
570 | * Rule 7: `BankResponse 'BankNo' -> RejectPhysicalMoney;`
|
571 | * Rule 8: `BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;`
|
572 | * Rules 9-10: `BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;`
|
573 | * Rule 11: `BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;`
|
574 |
|
575 | Or, as a block,
|
576 |
|
577 | ```fsl
|
578 | MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;
|
579 |
|
580 | TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
|
581 | TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;
|
582 |
|
583 | BankResponse 'BankNo' -> RejectPhysicalMoney;
|
584 | BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
|
585 | BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;
|
586 |
|
587 | BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;
|
588 | ```
|
589 |
|
590 | Which leaves us with the total code
|
591 |
|
592 |
|
593 | ```fsl
|
594 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
595 |
|
596 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
597 | HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
|
598 | HasCardNoAuth 'RightPIN' -> MainMenu;
|
599 |
|
600 | MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;
|
601 | MainMenu 'ExitReturnCard' -> EjectCardAndReset;
|
602 | MainMenu 'CheckBalance' -> PickCheckBalanceAccount -> DisplayBalance -> MainMenu;
|
603 |
|
604 | TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
|
605 | TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;
|
606 |
|
607 | BankResponse 'BankNo' -> RejectPhysicalMoney;
|
608 | BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
|
609 | BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;
|
610 |
|
611 | BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;
|
612 |
|
613 | EjectCardAndReset -> EmptyWaiting;
|
614 | ```
|
615 |
|
616 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/7_CanDepositMoney.png)
|
617 |
|
618 |
|
619 |
|
620 | <br/>
|
621 |
|
622 | #### 8: Can withdraw money from main menu
|
623 |
|
624 | Let's also be able to take money from the machine. After this, we'll move on, since our example is pretty squarely made
|
625 | by now.
|
626 |
|
627 | 1. Pick a withdrawl account, or cancel to the main menu
|
628 | 2. Shown a balance, pick a withdrawl amount, or cancel to acct picker
|
629 | 3. Is the withdrawl account too high? If so go to 2
|
630 | 4. Does the machine actually have the money? If not go to 2
|
631 | 5. Otherwise confirm intent w/ human
|
632 | 6. Attempt to post the transaction.
|
633 | 7. If fail, display reason and go to 1
|
634 | 8. If succeed, dispense money and go to main menu
|
635 |
|
636 | * Rules 1-3: `MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;`
|
637 | * Rule 4: `AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;`
|
638 | * Rule 5: `MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;`
|
639 | * Rule 6: `ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;`
|
640 | * Rule 7: `BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;`
|
641 | * Rule 8: `BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;`
|
642 |
|
643 | Rule 1 canceller: `PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu;`
|
644 | Rule 2 canceller: `PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount;`
|
645 |
|
646 | Or as a whole, we're adding
|
647 |
|
648 | ```fsl
|
649 | MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;
|
650 | AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;
|
651 | MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;
|
652 | ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;
|
653 | BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;
|
654 | BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;
|
655 |
|
656 | PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu;
|
657 | PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount;
|
658 | ```
|
659 |
|
660 | Which leaves us with
|
661 |
|
662 | ```fsl
|
663 | EmptyWaiting 'Wait' -> EmptyWaiting 'InsertCard' -> HasCardNoAuth;
|
664 |
|
665 | HasCardNoAuth 'CancelAuthReturnCard' -> EjectCardAndReset;
|
666 | HasCardNoAuth 'WrongPIN' -> HasCardNoAuth;
|
667 | HasCardNoAuth 'RightPIN' -> MainMenu;
|
668 |
|
669 | MainMenu 'AcceptDeposit' -> TentativeAcceptMoney;
|
670 | MainMenu 'ExitReturnCard' -> EjectCardAndReset;
|
671 | MainMenu 'CheckBalance' -> PickCheckBalanceAccount -> DisplayBalance -> MainMenu;
|
672 |
|
673 | TentativeAcceptMoney 'AcceptFail' -> RejectPhysicalMoney -> MainMenu;
|
674 | TentativeAcceptMoney 'AcceptSucceed' -> PickDepositAccount -> RequestValue 'TellBank' -> BankResponse;
|
675 |
|
676 | BankResponse 'BankNo' -> RejectPhysicalMoney;
|
677 | BankResponse 'BankYes' -> ConsumeMoney -> NotifyConsumed -> MainMenu;
|
678 | BankResponse 'BankAudit' -> BankAuditOffer 'HumanAcceptAudit' -> ConsumeMoney;
|
679 |
|
680 | BankAuditOffer 'HumanRejectAudit' -> RejectPhysicalMoney;
|
681 |
|
682 | MainMenu -> PickWithdrawlAccount -> PickAmount -> AcctHasMoney? 'TooHighForAcct' -> PickWithdrawlAccount;
|
683 | AcctHasMoney? -> MachineHasMoney? 'MachineLowOnCash' -> PickAmount;
|
684 | MachineHasMoney? -> ConfirmWithdrawWithHuman 'MakeChanges' -> PickWithdrawlAmount;
|
685 | ConfirmWithdrawWithHuman 'PostWithdrawl' -> BankWithdrawlResponse;
|
686 | BankWithdrawlResponse 'WithdrawlFailure' -> WithdrawlFailureExplanation -> PickWithdrawlAccount;
|
687 | BankWithdrawlResponse 'WithdrawlSuccess' -> DispenseMoney -> MainMenu;
|
688 |
|
689 | PickWithdrawlAccount 'CancelWithdrawl' -> MainMenu;
|
690 | PickWithdrawlAmount 'SwitchAccounts' -> PickWithdrawlAccount;
|
691 |
|
692 | EjectCardAndReset -> EmptyWaiting;
|
693 | ```
|
694 |
|
695 | ![](https://raw.githubusercontent.com/StoneCypher/jssm/master/src/assets/atm%20quick%20start%20tutorial/8_CanWithdrawMoney.png)
|
696 |
|
697 | As you can see, building up even very complex state machines is actually relatively straightforward, in a short
|
698 | amount of time.
|
699 |
|
700 |
|
701 |
|
702 | <br/><br/>
|
703 |
|
704 | ## Features
|
705 | ### DSL
|
706 | ### States
|
707 | ### Transitions
|
708 | ### Cycles
|
709 | ### Stripes
|
710 | ### Named Ordered Lists
|
711 | ### Atoms
|
712 | ### Strings
|
713 | ### Arrow types
|
714 | ### Unicode representations
|
715 | ### Node declarations
|
716 | ### All the styling bullshit
|
717 | ### Named edges
|
718 | ### URL callouts
|
719 | ### The 9 or whatever directives
|
720 | ### How to publish a machine
|
721 | #### Legal, main, and forced
|
722 | ### Validators
|
723 | ### State history
|
724 | ### Automatic visualization
|
725 |
|
726 |
|
727 |
|
728 | <br/><br/>
|
729 |
|
730 | ## How to think in state machines
|
731 |
|
732 |
|
733 |
|
734 | <br/><br/>
|
735 |
|
736 | ## Example Machines
|
737 | ### Door lock
|
738 | ### Traffic lights
|
739 | #### Basic three-state
|
740 | #### RYG, Off, Flash-red, Flash-yellow
|
741 | #### RYG, Off, Flash-red, Flash-yellow, Green-left, Yellow-left
|
742 | #### Heirarchal intersection
|
743 | ### [ATM](https://people.engr.ncsu.edu/efg/210/s99/Notes/fsm/atm.gif)
|
744 | ### [HTTP](https://www.w3.org/Library/User/Architecture/HTTP.gif)
|
745 | #### Better HTTP
|
746 | ### [TCP](http://www.texample.net/media/tikz/examples/PNG/tcp-state-machine.png)
|
747 | ### Coin-op vending machine (data)
|
748 | ### Video games
|
749 | #### Pac-man Ghost (sensors)
|
750 | #### Weather (probabilistics)
|
751 | #### Roguelike monster (interface satisfaction)
|
752 | ### Candy crush clone game flow (practical large use)
|
753 | ### Vegas locked 21 dealer behavior
|
754 | ### React SPA website (practical large use)
|
755 | ### [BGP](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/BGP_FSM.svg/549px-BGP_FSM.svg.png)
|
756 | ### [LibGCrypt FIPS mode FSM](https://www.gnupg.org/documentation/manuals/gcrypt/fips-fsm.png)
|
757 |
|
758 |
|
759 |
|
760 | <br/><br/>
|
761 |
|
762 | ## How to debug
|
763 |
|
764 |
|
765 |
|
766 | <br/><br/>
|
767 |
|
768 | ## How to publish
|
769 | It's really quite simple.
|
770 |
|
771 | 1. Make a github repository.
|
772 | 1. Put your code in a file inside, with the extension `.fsl`
|
773 | 1. Make sure your code contains a `machine_name`
|
774 |
|
775 | Once done, your work should show up [here](https://github.com/search?utf8=%E2%9C%93&q=extension%3Afsl+machine_name&type=Code).
|
776 |
|
777 |
|
778 |
|
779 | <br/><br/>
|
780 |
|
781 | ## Notation Comparison
|
782 | ### Their notations, one by one
|
783 | ### Apples to Apples - Traffic Light
|
784 |
|
785 |
|
786 |
|
787 | <br/><br/>
|
788 |
|
789 | ## Other state machines
|
790 | There are a lot of state machine impls for JS, many quite a bit more mature than this one. Here are some options:
|
791 |
|
792 | 1. [Finity](https://github.com/nickuraltsev/finity) 😮
|
793 | 1. [Stately.js](https://github.com/fschaefer/Stately.js)
|
794 | 1. [machina.js](https://github.com/ifandelse/machina.js)
|
795 | 1. [Pastafarian](https://github.com/orbitbot/pastafarian)
|
796 | 1. [Henderson](https://github.com/orbitbot/henderson)
|
797 | 1. [fsm-as-promised](https://github.com/vstirbu/fsm-as-promised)
|
798 | 1. [state-machine](https://github.com/DEADB17/state-machine)
|
799 | 1. [mood](https://github.com/bredele/mood)
|
800 | 1. [FSM Workbench](https://github.com/MatthewHepburn/FSM-Workbench)
|
801 | 1. [SimpleStateMachine](https://github.com/ccnokes/SimpleStateMachine)
|
802 | 1. shime/[micro-machine](https://github.com/shime/micro-machine)
|
803 | 1. soveran/[micromachine](https://github.com/soveran/micromachine) (ruby)
|
804 | 1. fabiospampinato/[FSM](https://github.com/fabiospampinato/FSM)
|
805 | 1. HQarroum/[FSM](https://github.com/HQarroum/Fsm)
|
806 | 1. [Finite-State-Automata](https://github.com/RolandR/Finite-State-Automata)
|
807 | 1. [finite-state-machine](https://github.com/MarkH817/finite-state-machine)
|
808 | 1. [nfm](https://github.com/ajauhri/nfm)
|
809 |
|
810 |
|
811 | And some similar stuff:
|
812 | 1. [redux-machine](https://github.com/mheiber/redux-machine)
|
813 | 1. [ember-fsm](https://github.com/heycarsten/ember-fsm)
|
814 | 1. [State machine cat](https://github.com/sverweij/state-machine-cat)
|
815 | 1. [Workty](https://github.com/AlexLevshin/workty) 😮
|
816 | 1. [sam-simpler](https://github.com/sladiri/sam-simpler)
|
817 | 1. [event_chain](https://github.com/quilin/event_chain)
|
818 | 1. [DRAKON](https://en.wikipedia.org/wiki/DRAKON)
|
819 | 1. [Yakindu Statechart Tools](https://github.com/Yakindu/statecharts)
|
820 | 1. [GraphViz](http://www.graphviz.org/)
|
821 | 1. [Viz.js](https://github.com/mdaines/viz.js/), which we use
|
822 |
|
823 |
|
824 |
|
825 | <br/><br/><br/>
|
826 |
|
827 | # Thanks
|
828 |
|
829 | JSSM and FSL have had a lot of help.
|
830 |
|
831 |
|
832 |
|
833 | <br/><br/>
|
834 |
|
835 | ## Internationalization
|
836 |
|
837 | * [Mykhaylo Les](https://github.com/miles91) provided three translation test cases ([Ukrainian](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/ukrainian.json), [Belarussian](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/belarussian.json), and [Russian](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/russian.json),) and the corresponding Traffic Light translations (also [Ukrainian](https://github.com/StoneCypher/fsl_traffic_light_ukrainian/blob/master/traffic%20light.fsl), [Belarussian](https://github.com/StoneCypher/fsl_traffic_light_belarussian/blob/master/traffic_light.fsl), and [Russian](https://github.com/StoneCypher/fsl_traffic_light_russian/blob/master/traffic%20light.fsl).)
|
838 | * [Tanvir Islam](https://github.com/tanvirrb) provided the [Bengali test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/bengali.json), translated the [Traffic Light](https://github.com/tanvirrb/fsl-traffic-light-bengali/blob/master/traffic_light.fsl) to Bengali, and published the first non-English `FSL` machine, in Bengali.
|
839 | * [Francisco Junior](https://github.com/fcojr) provided the [Portuguese test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/portuguese.json) and translated the [Traffic Light](https://github.com/StoneCypher/fsl_traffic_light_portuguese/blob/master/traffic_light.fsl) to Portuguese
|
840 | * [Jeff Katz](https://github.com/cohendvir) provided the [German test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/german.json).
|
841 | * [Alex Cresswell](https://github.com/technophile77) provdied the [Spanish test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/spanish.json)
|
842 | * [Dvir Cohen](https://github.com/cohendvir) provided the [Hebrew test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/hebrew.json).
|
843 | * [David de la Peña](https://github.com/daviddelapena) provided the [French test case](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/french.json)
|
844 |
|
845 | If I've overlooked you, please let me know.
|
846 |
|
847 | If you'd like to help, it's straightforward.
|
848 |
|
849 | 1. Easy mode: open a PR with [this file](https://github.com/StoneCypher/jssm/blob/master/src/js/tests/language_data/english.json) translated into your language
|
850 | 1. Extra mile: create a new repo containing [this file](https://github.com/StoneCypher/fsl_traffic_light/blob/master/traffic_light.fsl) translated
|
851 |
|
852 |
|
853 |
|
854 | <br/><br/>
|
855 |
|
856 | ## Code and Language
|
857 |
|
858 | [Forest Belton](https://github.com/forestbelton) has provided guidance, bugfixes, parser and language commentary.
|
859 |
|
860 | [Jordan Harbrand](https://github.com/ljharb) suggested two interesting features and provided strong feedback on the initial tutorial draft.
|
861 |
|
862 | The biggest thanks must go to [Michael Morgan](https://github.com/msmorgan/), who has debated significant sections of
|
863 | the notation, invented several concepts and operators, helped with the parser, with system nomenclature, for having published
|
864 | the first not-by-me `FSL` machine, for encouragement, and generally just for having been as interested as he has been.
|