├── .eslintrc.yaml ├── .gitignore ├── .tern-project ├── CNAME ├── LICENSE ├── README.md ├── index.html ├── package.json ├── src ├── DocumentMenu.js ├── TMDocument.js ├── TMDocumentController.js ├── TMSimulator.js ├── TMViz.js ├── TuringMachine.js ├── examples.js ├── examples │ ├── _template.yaml │ ├── binaryAdd.yaml │ ├── binaryIncrement.yaml │ ├── binaryMult.yaml │ ├── busyBeaver3.yaml │ ├── busyBeaver4.yaml │ ├── copy1s.yaml │ ├── divisibleBy3.yaml │ ├── divisibleBy3Base10.yaml │ ├── lengthMult.yaml │ ├── matchBinaryStrings.yaml │ ├── matchThreeLengths.yaml │ ├── palindrome.yaml │ ├── powersOfTwo.yaml │ ├── repeat01.yaml │ └── unaryMult.yaml ├── kbdshortcuts.js ├── main.js ├── parser.js ├── sharing │ ├── CheckboxTable.js │ ├── FileReaderPromise.js │ ├── export-panel.js │ ├── format.js │ ├── gist.js │ ├── import-panel.js │ └── import.js ├── state-diagram │ ├── StateGraph.js │ ├── StateViz.css │ └── StateViz.js ├── storage.js ├── tape │ ├── Tape.js │ ├── TapeViz.js │ └── tape.css ├── util.js └── watch.js ├── update-gh-pages.sh └── webpack.config.js /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | ## Stylistic ## 4 | indent: 5 | - 1 6 | - 2 7 | - SwitchCase: 1 8 | VariableDeclarator: 9 | var: 2 10 | quotes: 11 | - 1 12 | - single 13 | linebreak-style: 14 | - 2 15 | - unix 16 | semi: 17 | - 1 18 | - always 19 | space-before-function-paren: 20 | - 1 21 | - anonymous: always 22 | named: never 23 | consistent-this: 24 | - 1 25 | - self 26 | ## Semantic ## 27 | no-invalid-this: 2 # prevent gotcha: closures inside methods don't inherit 'this' 28 | no-shadow: 29 | - 2 30 | - builtinGlobals: true 31 | no-shadow-restricted-names: 2 32 | no-unexpected-multiline: 2 33 | strict: 34 | - 2 35 | - global 36 | env: 37 | commonjs: true 38 | extends: eslint:recommended 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules/ 3 | .eslintcache 4 | 5 | 6 | /build/ 7 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser", 4 | "jquery" 5 | ], 6 | "loadEagerly": [ 7 | "src/*.js" 8 | ], 9 | "dontLoad": [ 10 | "node_modules/" 11 | ], 12 | "plugins": { 13 | "doc_comment": { 14 | "fullDocs": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | turingmachine.io 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright 2015-2021, Andy Li 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [turingmachine.io](http://turingmachine.io) 2 | 3 | This is a [Turing machine] visualizer designed for learning through visual thinking and creative exploration. 4 | 5 | Machines are described in a simple YAML-based format. 6 | As you code, each save updates the state diagram; this offers the speed and directness of code, combined with the visual intuitiveness of a graphical editor. 7 | 8 | Multiple example machines are provided, each one with commentary that touches on concepts like subroutines and inductive definitions / recursion. 9 | Many examples include exercises that build on the machines and deepen understanding. 10 | To encourage experimentation, the document system provides for quick snapshots and autosaving to browser local storage. 11 | 12 | All in all, this is the simulator I wish I had when taking automata theory. 13 | At the same time, I’ve tried to make it accessible to people who aren’t in computer science, or haven’t heard of a Turing machine before. 14 | 15 | Feel free to email me if you have any questions, comments, or feedback in general about the project. 16 | Bug reports and feature requests are also welcome on the [issue tracker]. 17 | Some known issues and ideas for improvement are outlined on the [wiki]. 18 | 19 | [Turing machine]: http://plato.stanford.edu/entries/turing-machine 20 | 21 | [issue tracker]: https://github.com/aepsilon/turing-machine-viz/issues 22 | [wiki]: https://github.com/aepsilon/turing-machine-viz/wiki 23 | 24 | 25 | ## Development Setup 26 | 27 | If you want to work on the site itself, here’s how to get started: 28 | 29 | Clone the repo and run `npm install` in the folder. Afterwards, use `npm start` to host the site locally on a [webpack server], by default at localhost:8080. 30 | 31 | `npm run depgraph` or `depgraph-noext` (requires [madge] and [Graphviz]) produces 32 | a visual dependency graph that’s good for getting a feel for the code layout. 33 | 34 | [webpack server]: https://webpack.github.io/docs/webpack-dev-server.html 35 | [madge]: https://github.com/pahen/madge 36 | [Graphviz]: http://www.graphviz.org/ 37 | 38 | 39 | ## Dependencies 40 | 41 | Thanks go to the authors of the following runtime dependencies: 42 | 43 | * [Ace] code editor 44 | * [bluebird.js] cancellable promises 45 | * [Bootstrap] with the [lumen] theme 46 | * [clipboard.js] one-click copy to clipboard 47 | * [D3] visualization and DOM manipulation library 48 | * [jQuery] 49 | * [js-yaml] parser & serializer 50 | * [lodash] and [lodash/fp] utilities 51 | 52 | [Ace]: https://ace.c9.io/ 53 | [bluebird.js]: http://bluebirdjs.com/ 54 | [Bootstrap]: https://getbootstrap.com/ 55 | [clipboard.js]: https://clipboardjs.com/ 56 | [D3]: https://d3js.org/ 57 | [jQuery]: https://jquery.com 58 | [js-yaml]: https://github.com/nodeca/js-yaml 59 | [lodash]: https://github.com/lodash/lodash 60 | [lodash/fp]: https://github.com/lodash/lodash/wiki/FP-Guide 61 | [lumen]: https://bootswatch.com/lumen/ 62 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | Turing machine visualization 13 | 14 | 15 | 17 | 19 | 20 | 21 | 25 | 26 | 27 | 29 | 31 | 32 | 34 | 36 | 37 | 39 | 41 | 43 | 44 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 189 | 190 | 200 | 201 | 202 | 203 | 204 | 205 | 249 | 250 | 251 | 267 | 268 | 287 | 288 | 310 | 311 | 368 | 369 | 453 | 454 | 455 |
456 |
457 |
458 | 459 |
460 | 461 | 471 |
472 | 473 |
474 |
475 | 479 |
480 |
481 | 485 |
486 |
487 | 491 |
492 |
493 |
494 | 495 | 496 |
497 |
498 |
499 | 502 | 505 |
506 |
507 |
508 |
509 | 510 | 519 | 520 | 521 | 522 | 523 |
524 | 525 |
526 |
527 |

Try it out

528 |

529 | Run the machine to see it in action. 530 | At any time, you can step or pause to get a closer look, 531 | or reset to start over. 532 |

533 |

There are over a dozen different example machines to explore.

534 |

535 | Most of the examples take input. Experiment with different inputs to see what happens! 536 | Edit the code and click Load machine to sync your changes. 537 |

538 |
539 |

What’s going on?

540 |

The colored circles are states. The squares underneath are tape cells.

541 |

The current state and tape cell are highlighted.

542 |

At each step, a Turing machine reads its current state and tape symbol, 543 | and looks them up in its transition table for an instruction. 544 | Each instruction does 3 things:

545 |
    546 |
  1. write a symbol to the current tape cell
  2. 547 |
  3. move to the left or right by one cell
  4. 548 |
  5. set the new state
  6. 549 |
550 |

That’s it!

551 |

This repeats step after step, until the machine reaches a combination of state and symbol 552 | that don’t have an instruction defined. At that point it halts. 553 |

554 |
555 |
556 |

559 |
560 |
561 | 568 |
569 |
570 |

A Turing machine is an abstract device to model computation as rote symbol manipulation.

571 |

Each machine has a finite number of states, and a finite number of possible symbols. 572 | These are fixed before the machine starts, and do not change as the machine runs.

573 |

There are an infinite number of tape cells, however, extending endlessly to the left and right. 574 | Each tape cell bears a symbol. Any cell not part of the input or not yet written to bears the blank symbol by default. 575 | Notice that at any step, only finitely many cells bear a non-blank symbol.

576 |

(As a mathematical model, a Turing machine has infinite memory (an infinite tape) so as to not artificially restrict its power. 577 | In practice, many machines of interest take finite memory, and can be fully simulated for manageable input sizes. 578 | Even machines that use infinite memory—and hence never halt—use at most one new tape cell per step, and so can be simulated to an extent.)

579 |
580 |
581 |
582 |
583 | 590 |
591 |
592 |

The formal definition of a Turing machine has slight variations but essentially is a tuple (ordered list) comprising

593 |
    594 |
  • states Q
  • 595 |
  • start state q₀ ∈ Q
  • 596 |
  • input alphabet Σ
  • 597 |
  • tape alphabet Γ, where Σ ⊆ Γ
  • 598 |
  • blank symbol b ∈ Γ
  • 599 |
  • transition function δ that has the type Q × Γ → Γ × {L, R} × Q
  • 600 |
601 |

where Q, Σ, and Γ are finite nonempty sets. 602 | Some definitions also require that the blank symbol not be part of the input (b ∉ Σ).

603 | 604 |

As an example, a formal description for the “binary increment” machine is as follows:

605 |

606 | Q = { right, carry, done } 607 |
q₀ = right 608 |
Σ = { 1, 0 } 609 |
Γ = { 1, 0, ' ' } 610 |
b = ' ' 611 |

612 |

613 | δ(right, 1) = (1, R, right) 614 |
δ(right, 0) = (0, R, right) 615 |
δ(right, ' ') = (' ', L, carry) 616 |
δ(carry, 1) = (0, L, carry) 617 |
δ(carry, 0) = (1, L, done) 618 |
δ(carry, ' ') = (1, L, done) 619 |

620 |

Note that for simplicity, the simulator limits each symbol to one character. 621 | Furthermore, input is not checked for conformance with an input alphabet, in exchange for not having to define input and tape alphabets.

622 |

(The behavior of a Turing machine can also be described in mathematical terms if desired. 623 | Without going into too much detail, this involves defining how a configuration 624 | of a machine—its state, tape contents, and tape head position (current cell)—leads to the next configuration, based on the transition function. 625 | To start with, the tape’s contents may be defined as a function from integers to symbols, 626 | with the head position as an integer, and each move {L, R} adding -1 or +1 respectively to the head position.)

627 |
628 |
629 |
630 |
631 | 638 |
639 |
640 |

Some aspects of the definition vary from author to author, but the differences come down to preference and do not affect computational power. 641 | That is, machines on one model can be simulated or converted to run on another model.

642 |

Some of the variations you may come across:

643 |
    644 |
  1. The tape only extends infinitely to the right. The left end is where the tape head (current cell) begins. 645 | Moving left at the left end keeps the tape head on the same cell.
  2. 646 |
  3. In addition to “move left” (L) or “move right” (R), a tape head movement can be “no move” (N).
  4. 647 |
  5. Instead of moving the tape head, a movement shifts the tape itself, so that the directions of L & R are swapped. 648 | (Shifting the tape left is the same as moving the head right.)
  6. 649 |
  7. The machine can only halt in one of two states: a state designated the accept state, or another state designated the reject state. 650 | These states have no exiting transitions. All the other states must have transitions defined for every symbol.
  8. 651 |
652 |

The simulator was designed with these and other considerations in mind.

653 |
    654 |
  1. Limiting the tape was inconvenient, often requiring marking the left end with a symbol, and writing numbers in reverse. 655 | On the other hand, machines created for a right-infinite tape can run on a doubly-infinite tape with little to no modification.
  2. 656 |
  3. Compared to L and R, “no move” (N) seems to be seldom used. For now it has been omitted for conceptual simplicity.
  4. 657 |
  5. It's more intuitive to set the current cell directly by moving the tape head. Turing also follows this convention. 658 | (The visualization however stays centered on the head to avoid issues of the head moving out of view.)
  6. 659 |
  7. The accept/reject convention still works; it's just not required. 660 | In fact the simulator supports a convenient shorthand, demonstrated in some of the examples: 661 | omit the reject state and all transitions to it, and treat halting on a non-accept state as an implicit rejecting transition. 662 | This reduces tedious boilerplate code and makes for a cleaner diagram.
  8. 663 |
664 |
665 |
666 |
667 |
668 |
669 |

670 | For a very readable introduction to Turing machines, including their significance and interesting implications, 671 | see the excellent entry in the Stanford Encyclopedia of Philosophy. 672 |

673 |
674 | 675 | 676 | 677 |
678 |

Make your own

679 |

Make spinoffs from the examples or your own creations by using 680 | Edit > Duplicate document. 681 | You can also start from scratch with a new blank document.

682 |

All it takes to describe a Turing machine is a start state, blank symbol, and transition table.

683 |

Example

684 |
685 | # Adds 1 to a binary number.
686 | input: '1011'
687 | blank: ' '
688 | start state: right
689 | table:
690 |   # scan to the rightmost digit
691 |   right:
692 |     1: R
693 |     0: R
694 |     ' ': {L: carry}
695 |   # then carry the 1
696 |   carry:
697 |     1: {write: 0, L}
698 |     0: {write: 1, L: done}
699 |     ' ': {write: 1, L: done}
700 |   done:
701 | 
702 |

703 | Here the states are right, carry, and done.
704 | The symbols are '1', '0', and ' '. 705 |

706 |

707 | We designate one state the start state, 708 | and one symbol the blank symbol (found on unmarked tape cells). 709 |

710 |

711 | A state and a symbol together map to an instruction. 712 | In the carry state, for instance, the symbol 1 maps to the instruction {write: 0, L}. 713 | When no instruction is defined, as in the done state, the machine halts. 714 |

715 |

Instruction format

716 | The general form of an instruction has three parts: 717 |
{write: symbol, move: state}
718 |
    719 |
  • {write: 1, L: done} writes the symbol 1, 720 | moves the tape head left (L), and goes to the done state. 721 |
  • 722 |
723 |

For brevity, you can omit the symbol and state if they stay the same.

724 |
    725 |
  • {L: carry} writes the same symbol that was read, 726 | moves the tape head left, and goes to the carry state.
  • 727 |
  • R (shorthand for {R}) simply moves the tape head right. 728 | It writes back the same symbol and stays in the same state.
  • 729 |
730 |
731 | 732 | 733 | 734 |
735 |

Tips

736 |
737 |
738 |

Editor keyboard shortcuts

739 | 740 |
741 | 744 |

More keyboard shortcuts are available on the full list.

745 |
746 |
747 |

Misc. tips

748 |
    749 |
  • Drag or click a state node to fix it in place; double-click to release it.
  • 750 |
  • Don’t hesitate to duplicate. 751 | Use Edit > Duplicate document to create a snapshot of your current document whenever you’re about to make a larger edit.
  • 752 |
  • Changes are autosaved to your browser’s local storage. 753 | They will stay there between sessions, but be careful when clearing browser data. You can create backups by downloading your documents.
  • 754 |
  • Incognito / Private Browsing mode uses a separate temporary storage. This is useful for importing or editing documents that you want to try but don’t want to keep.
  • 755 |
756 | 759 |
760 |

The code is written in YAML 1.2, a general-purpose data format.

761 |

762 | If you’re familiar with JSON, YAML is similar, but designed to be more readable. 763 | Mappings can use indent instead of brackets {}, and strings can often be included directly without quotes. 764 |

765 |

766 | Some strings need to be quoted: for example, those that start/end with a space, or include certain characters that have special meaning in YAML. 767 | If a string is causing problems, try putting quotes around it. 768 |

769 |
770 |
771 |
772 |
773 |
774 |
775 | 776 | 783 | 784 | 785 | 800 | 801 | 802 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turing-machine-viz", 3 | "version": "1.0.0", 4 | "description": "Turing machine visualization and simulator", 5 | "homepage": "http://turingmachine.io", 6 | "license": "BSD-3-Clause", 7 | "author": "Andy Li ", 8 | "repository": "aepsilon/turing-machine-viz", 9 | "scripts": { 10 | "clean": "trash build/ || rm -r build/", 11 | "depgraph": "mkdir -p build/ && (cd src/ && madge . --dot) > build/depgraph.gv", 12 | "depgraph-noext": "mkdir -p build/ && (cd src/ && madge . --dot -x \"`node -e \"console.log(Object.keys(require('../webpack.config').externals).map(s => '^'+s+'$').join('|'))\"`\") > build/depgraph-noext.gv", 13 | "lint": "eslint --cache webpack.config.js src/", 14 | "prod": "export NODE_ENV=production; npm run lint && webpack --progress --colors --display-error-details", 15 | "start": "webpack-dev-server --progress --colors --display-error-details --host=0.0.0.0", 16 | "watch": "webpack --watch --progress --colors --display-error-details" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "eslint": "^3.0.0", 21 | "file-loader": "^0.8.5", 22 | "raw-loader": "^0.5.1", 23 | "webpack": "^1.12.9", 24 | "webpack-dev-server": "^1.16.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DocumentMenu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global document */ 4 | var KeyValueStorage = require('./storage').KeyValueStorage; 5 | var TMDocument = require('./TMDocument'); 6 | var d3 = require('d3'); 7 | var defaults = require('lodash/fp').defaults; // NB. 2nd arg takes precedence 8 | 9 | /** 10 | * Document menu controller. 11 | * 12 | * The view is fully determined by a 3-tuple: ([ID], ID -> Name, currentID). 13 | * @constructor 14 | * @param {Object} args argument object 15 | * @param {HTMLSelectElement} 16 | * args.menu 17 | * @param {?Node} [args.group=args.menu] Node to add documents to. 18 | * @param {string} args.storagePrefix 19 | * @param {?(TMDocument -> HTMLOptionElement)} 20 | * args.makeOption Customize rendering for each document entry. 21 | * @param {?string} args.firsttimeDocID Document to open on the first visit. 22 | */ 23 | function DocumentMenu(args) { 24 | var menu = args.menu, 25 | group = args.group || menu, 26 | storagePrefix = args.storagePrefix, 27 | firsttimeDocID = args.firsttimeDocID; 28 | 29 | if (!menu) { 30 | throw new TypeError('DocumentMenu: missing parameter: menu element'); 31 | } else if (!storagePrefix) { 32 | throw new TypeError('DocumentMenu: missing parameter: storage prefix'); 33 | } 34 | if (args.makeOption) { 35 | this.optionFromDocument = args.makeOption; 36 | } 37 | this.menu = menu; 38 | this.group = group; 39 | this.group.innerHTML = ''; 40 | this.__storagePrefix = storagePrefix; 41 | 42 | // Load document entries (non-examples) 43 | this.doclist = new DocumentList(storagePrefix + '.list'); 44 | this.render(); 45 | // Re-open last-opened document 46 | this.selectDocID(this.getSavedCurrentDocID() || firsttimeDocID); 47 | 48 | // Listen for selection changes 49 | var self = this; 50 | this.menu.addEventListener('change', function () { 51 | self.onChange(self.currentDocument, {type: 'open'}); 52 | }); 53 | 54 | // Listen for storage changes in other tabs/windows 55 | KeyValueStorage.addStorageListener(function (e) { 56 | var docID; 57 | var option, newOption; 58 | 59 | if (e.key === self.doclist.storageKey) { 60 | // case: [ID] list changed 61 | self.doclist.readList(); 62 | self.render(); 63 | } else if ( (docID = TMDocument.IDFromNameStorageKey(e.key)) ) { 64 | // case: single document renamed: (ID -> Name) changed 65 | option = self.findOptionByDocID(docID); 66 | if (option) { 67 | // replace the whole