├── GUIDE.md ├── LICENSE.md ├── README.md ├── TODO.md ├── abbreviated.go ├── abbreviated_test.go ├── beaver.go ├── beaver_test.go ├── decision.go ├── decision_test.go ├── diagonal.go ├── diagonal_test.go ├── go.mod ├── hilbert.go ├── hilbert_test.go ├── lambda.go ├── lambda_test.go ├── machine.go ├── machine_test.go ├── standard.go ├── standard_test.go ├── universal.go └── universal_test.go /GUIDE.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | 3 | This guide and codebase is a companion resource for those reading [On Computable Numbers, with an Application to the Entscheidungsproblem](https://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf) by Alan Turing. 4 | 5 | I can't recommend Charles Petzold's [The Annotated Turing](https://www.amazon.com/Annotated-Turing-Through-Historic-Computability/dp/0470229055) enough, this project would not have been possible without it. 6 | 7 | The guide annotates the paper section-by-section. I only quote directly from the paper when there is something to call out explicitly. When possible I bias towards a fully working implementation in code that the reader can use themselves. 8 | 9 | ## Introduction 10 | 11 | ### What are computable numbers? 12 | 13 | Turing explains that his paper will be about [computable numbers](https://en.wikipedia.org/wiki/Computable_number). He briefly discusses [computable functions](https://en.wikipedia.org/wiki/Computable_function) saying they share the same fundamental problems, however he chooses computable numbers as the subject of the paper as they involve "the least cumbersome technique". 14 | 15 | > The "computable" numbers may be described briefly as the real numbers whose expressions as a decimal are calculable by finite means. 16 | 17 | #### Number Theory 101 18 | 19 | - $\mathbb{N}$ ([naturals](https://en.wikipedia.org/wiki/Natural_number)): $0$, $1$, $2$, ... 20 | - $\mathbb{Z}$ ([integers](https://en.wikipedia.org/wiki/Integer)): $-2$, $-1$, $0$, $1$, $2$, ... 21 | - $\mathbb{Q}$ ([rationals](https://en.wikipedia.org/wiki/Rational_number)): $-1$, $1$, $\tfrac{1}{2}$, $\tfrac{7}{44}$, ... 22 | - $\mathbb{R}$ ([reals](https://en.wikipedia.org/wiki/Real_number)): $-1$, $1$, ${\sqrt {2}}$, $\pi$, ... 23 | - $\mathbb{C}$ ([complex](https://en.wikipedia.org/wiki/Complex_number)): $-1$, $1$, ${\sqrt {2}}$, $\pi$, $i$, $2i+3$, ... 24 | 25 | The real numbers are all numbers which are not imaginary, and our "computable" numbers are a subset of the reals. When he says "expressions as a decimal", he is simply saying that he wants to deal with strings of digits ($0.333...$ rather than $\tfrac{1}{3}$) and in fact he will further limit his numbers to just binary digits ($0.010101...$). 26 | 27 | By "finite means" Turing means that there must be some rule to arrive at the number without just infintely listing every digit. For example, we can describe $0.0101010101...$ finitely by saying you can repeat $01$ infinitely. 28 | 29 | Of course, there are infinitely *random* real numbers. Whether or not we can calculate these numbers by finite means is a major area of the paper. 30 | 31 | > According to my definition, a number is computable 32 | if its decimal can be written down by a machine. 33 | 34 | Turing is giving a sneak peak to the reader, telling you he will define a "machine" which will embody the calculability by finite means. 35 | 36 | He goes on to say he will prove that large classes of the reals are computable. He then gives another sneak peak: 37 | 38 | > The computable numbers do not, however, include 39 | all definable numbers, and an example is given of a definable number 40 | which is not computable. 41 | 42 | Turing is saying he will give an example of a real number his machine cannot compute. 43 | 44 | ### Enumerabililty 45 | 46 | > Although the class of computable numbers is so great, and in many 47 | ways similar to the class of real numbers, it is nevertheless enumerable. 48 | In §8 I examine certain arguments which would seem to prove the contrary. 49 | 50 | Turing here says that computable numbers are enumerable, and then says there are arguments that prove they are not enumerable. Which is it? 51 | 52 | By enumerable Turing means [countable](https://en.wikipedia.org/wiki/Countable_set) (I will not be providing a TL;DR on Set Theory here). 53 | 54 | > By the correct application of one of these arguments, conclusions are 55 | reached which are superficially similar to those of Gödel. 56 | 57 | Turing is referring to the [diagonalization](https://en.wikipedia.org/wiki/Diagonal_lemma) used in [Gödel's incompleteness theorems](https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems) which we will talk about in [section 8](./GUIDE.md#section-8---application-of-the-diagonal-process). 58 | 59 | ### The Entscheidungsproblem 60 | 61 | > These results have valuable applications. In particular, it can be shown (§11) that the 62 | Hilbertian Entscheidungsproblem can have no solution. 63 | 64 | Here Turing reveals **the point of the paper**. All of this work is to serve the purpose of giving a result to [the Entscheidungsproblem](https://en.wikipedia.org/wiki/Entscheidungsproblem) (in English "the *decision* problem"). We will talk in depth about the Entscheidungsproblem later in [section 11](./GUIDE.md#section-11---application-to-the-entscheidungsproblem), but here is a short description for now: 65 | 66 | The decision problem asks if it is possible for there to be an algorithm that decides if a logic statement is **provable** from a set of axioms, for every possible statement. Again, a shortened description of the algorithm: 67 | 68 | - Input: A logic statement, and a set of axioms 69 | - Output: A proof of the statement's truth (or falsity) based on the set of axioms, **or** simply a statement that the logic statement is not provable. 70 | 71 | Turing's answer to whether such an algorithm exists: *No*. 72 | 73 | ### Effective Calculability vs Computability 74 | 75 | Apparently within the same couple of months, [Alonzo Chuch](https://en.wikipedia.org/wiki/Alonzo_Church) also came to the conclusion that there is no solution to the decision problem, and published his paper first. Turing added an Appendix that explains how Church's paper compares to his own, and gives the reader a heads up here in the introduction. Read on to the [Appendix](./GUIDE.md#appendix---computability-and-effective-calculability) if this interests you. 76 | 77 | ## Section 1 - Computing machines 78 | 79 | ### Finite Means 80 | 81 | Turing begins this section with a preamble that says he won't attempt to justify the given definition of a computable number (one whose digits are calculable by finite means) until [section 9](./GUIDE.md#section-9---the-extent-of-computable-numbers). 82 | 83 | He says this though: 84 | 85 | > For the present I shall only say that the justification 86 | lies in the fact that the human memory is necessarily limited. 87 | 88 | A "computer" during Turing's time was an actual human performing calculations on pen and paper. 89 | 90 | This is quite philosophical but Turing is essentially just saying that the human mind is limited to finiteness in terms of the *means* of arriving at a number, for example: 91 | 92 | | | Finite Means | Infinite Means | 93 | |-| ------------ | -------------- | 94 | | Finite Number | $0.1$ | *Not possible* | 95 | | Infinite Number | $0.010101...$ | *Infinitely random* | 96 | 97 | ### The "Machine" 98 | 99 | Turing spends the remainder of the section giving a textual description of his "machines". These are of course the famous [Turing Machines](https://en.wikipedia.org/wiki/Turing_machine). I think his description is quite readable, so I won't try to explain it here. I will instead provide a simplified version of the type structure of the machine below (it can also be found at the top of [machine.go](./machine.go)). 100 | 101 | ```go 102 | type ( 103 | Machine struct { 104 | mConfigurations []MConfiguration 105 | tape Tape 106 | } 107 | 108 | MConfiguration struct { 109 | Name string 110 | Symbols []string 111 | Operations []string 112 | FinalMConfiguration string 113 | } 114 | 115 | Tape []string 116 | ) 117 | ``` 118 | 119 | ## Section 2 - Definitions 120 | 121 | Turing provides a list of definitions he will rely on later. This section is also very readable, so I'll just provide a quick reference and not belabor the details: 122 | 123 | - **Automatic machines (a-machines)** - Machines where humans are not in the loop. 124 | - **Computing machines** - Machines that print $0$ and $1$ as "figures", and also print other characters that help with computation. 125 | - **Sequence computed by the machine** - The sequence of "figures" (only $0$ and $1$) computed (i.e. $010101...$). 126 | - **Number computed by the machine** - The real number obtained by prepending a decimal place to the sequence (i.e. $0.010101...$). 127 | - **Complete configuration** - These three things which describe the full state of the machine: 128 | - The full tape up to this point 129 | - The number of the currently scanned square 130 | - The name of the current m-configuration 131 | - **Moves** - Changes of the machine and tape between successive complete 132 | configurations. 133 | - **Circular machine** - Machines that halt or do not print "figures" ($0$'s or $1$'s) infinitely. 134 | - **Circle-free machine** - Must print "figures" ($0$'s or $1$'s) infinitely. 135 | - **Computable sequence** - A sequence of "figures" that can be computed by a circle-free machine. 136 | - **Computable number** - A number that can be derived from a computable sequence. 137 | 138 | ## Section 3 - Examples of computing machines 139 | 140 | In this section Turing gives simple and concrete examples for his machines. 141 | 142 | A full implementation can be found in [machine.go](./machine.go) and [machine_test.go](./machine_test.go). 143 | 144 | **Note**: In this guide and throughout the codebase I use English letters only. Turing makes use of lowercase Greek, upper and lowercase German letters (`ə`, for example), and I will always use the English version, as its easier for me to type them. At some point I may replace all letters in the repository with Turing's original Greek/German characters. 145 | 146 | [machine.go](./machine.go) contains the machine's functionality: 147 | 148 | ```go 149 | // Creates a machine 150 | m := NewMachine(MachineInput{ ... }) 151 | 152 | // Moves the machine once 153 | m.Move() 154 | 155 | // Moves the machine 10 times 156 | m.MoveN(10) 157 | 158 | // Prints the Tape (as a string) 159 | fmt.Println(m.TapeString()) 160 | 161 | // Print's the machine's complete configuration 162 | fmt.Println(m.CompleteConfiguration()) 163 | ``` 164 | 165 | [machine_test.go](./machine_test.go) contains Turing's three machine examples, which we test to ensure our implementation works as expected. 166 | 167 | The final paragraph documents some conventions that Turing will always use with his machines, I'll repeat them here (along with others that he doesn't explicitly call out): 168 | 1. Begin the tape with `ee` so the machine can always find the start of the tape. 169 | 2. The square directly after `ee` is the first `F`-square (for figures). Only our sequence figures (`0` or `1`) will be printed on `F`-squares. `F`-squares will by-convention never be erased. 170 | 3. After every `F` square is an `E`-square (for erasable). In these squares Turing prints temporary characters to help with keeping track of things during computation. Otherwise they are always blank. After each `E`-square is another `F`-square. 171 | 4. We "mark" an `F`-square by placing a character directly to the right (that is, the `E`-square on the right). Turing uses "marking" extensively. 172 | 173 | Examples: 174 | 175 | ```go 176 | // Visually how to think about E-squares and F-squares 177 | "eeFEFEFEFE ..." 178 | // How it will look in practice 179 | "ee0 1 0 1x1x0 ..." 180 | ``` 181 | 182 | Here are some implementation details to note for our `Machine`: 183 | 184 | - Our m-configuration rows are stored in one giant list (rather than grouping each m-configuration of the same name in some structure). I found the independent m-configuration rows easier to implement. 185 | - For the `Symbols` m-configuration field, I require that ` ` (None) be provided in addition to `*` (Any), or `!x` (Not `x`) if the m-configuration should match the blank square. This is because exactly what is meant by None, Any, Not in Turing's machine is dependent on the other m-configurations of the same name. Our implementation depends on all m-configuration rows being independent from one another, as stated above. 186 | - Some optional fields are provided (and used by our implementation) to make things cleaner. These are `StartingMConfiguration`, `PossibleSymbols`, `NoneSymbol`, and `Debug`. These should be self-explanatory, and it should be clear by [section 7](./GUIDE.md#section-7---detailed-description-of-the-universal-machine) why they are necessary. 187 | - You may find that I ocassionaly use `halt` as a final-m-configuration, and I never define the actual m-configuration. This is because in Turing's machines, the only way for the machine to stop (or halt) is if we are unable to find the next m-configuration after a "move". By convention, whenever I want to configure a machine to stop at some point, I will use the undefined m-configuration `halt` (however our machine, like Turing's, halts when it encounters an m-configuration that was never defined). 188 | 189 | ## Section 4 - Abbreviated tables 190 | 191 | I got super tripped up by this section. Turing explains his "abbreviated tables" briefly and then piles them on hard. It is only with Petzold's help that I was able to figure out some of the nuances here. I'll try to start with simple example so we can work our way up (Turing starts with a complex one.) Turing's abbreviated table examples also build upon eachother (most are dependant on others) to accomplish something complex. Our four examples will be independent from one another to keep things as simple as possible. 192 | 193 | The full implementation for this section can be found in [abbreviated.go](./abbreviated.go) and [abbreviated_tests.go](./abbreviated_test.go). It works like this: 194 | 195 | ```go 196 | // NewAbbreviatedTable compiles our abbreviated table to normal MachineInput 197 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 198 | MConfigurations: { ... } 199 | })) 200 | 201 | // Machines "compiled" from abbreviated tables can do anything normal machines can do 202 | m.Move() 203 | ``` 204 | 205 | ### Example 1 - Substituting symbols in `Operations` 206 | ```go 207 | // This table moves to the right, prints `0` and repeats: 208 | MConfigurations{ 209 | { 210 | Name: "b", 211 | Symbols: []string{"*"}, 212 | Operations: []string{"R"}, 213 | FinalMConfiguration: "f(0)", 214 | }, 215 | { 216 | Name: "f(a)", 217 | Symbols: []string{"*"}, 218 | Operations: []string{"Pa"}, // We are printing whatever is passed to `f` 219 | FinalMConfiguration: "b", 220 | }, 221 | } 222 | 223 | // and compiles to: 224 | 225 | MConfigurations{ 226 | { 227 | Name: "b", 228 | Symbols: []string{"*"}, 229 | Operations: []string{}, 230 | FinalMConfiguration: "c", 231 | }, 232 | { 233 | Name: "c", 234 | Symbols: []string{"*"}, 235 | Operations: []string{"P0"}, // Note that we substituted `a` with `0` here 236 | FinalMConfiguration: "b", 237 | }, 238 | } 239 | 240 | // Above we have taken the liberty of choosing `c` as the "compiled" m-configuration name 241 | // for our m-function. Turing will just use `q1`, `q2`, `q3`, ... during compilation, so 242 | // our m-configuration should really look like: 243 | MConfigurations{ 244 | { 245 | Name: "q1", 246 | Symbols: []string{"*"}, 247 | Operations: []string{}, 248 | FinalMConfiguration: "q2", 249 | }, 250 | { 251 | Name: "q2", 252 | Symbols: []string{"*"}, 253 | Operations: []string{"P0"}, 254 | FinalMConfiguration: "q1", 255 | }, 256 | } 257 | ``` 258 | 259 | So our m-functions have names and parameters. They will be called by other m-configurations in the final-m-configuration column. 260 | 261 | ### Example 2 - Substituting a `FinalMConfiguration`: 262 | 263 | ```go 264 | // This table also moves to the right, prints `0` and repeats. 265 | MConfigurations{ 266 | { 267 | Name: "b", 268 | Symbols: []string{"*"}, 269 | Operations: []string{"R"}, 270 | FinalMConfiguration: "f(b)", // Pass the name of an m-configuration to `f` 271 | }, 272 | { 273 | Name: "f(A)", // Receive the m-configuration name as parameter `A` 274 | Symbols: []string{"*"}, 275 | Operations: []string{"P0"}, 276 | FinalMConfiguration: "A", // Move to `A` (which is really `b`) 277 | }, 278 | } 279 | 280 | // compiles to: 281 | 282 | MConfigurations{ 283 | { 284 | Name: "q1", 285 | Symbols: []string{"*"}, 286 | Operations: []string{"R"}, 287 | FinalMConfiguration: "q2", 288 | }, 289 | { 290 | Name: "q2", 291 | Symbols: []string{"*"}, 292 | Operations: []string{"P0"}, 293 | FinalMConfiguration: "q1", // Move back to `b` (the first m-configuration) 294 | }, 295 | } 296 | ``` 297 | Turing's convention with parameters is that symbols are lowercase (his are Greek, ours English) and m-configurations are uppercase (his are German, ours English). 298 | 299 | Note that in Turing's first example (`f`), there are a bunch of `f` rows, and then `f1` rows and `f2` rows. When he does this, he is saying that `f` is the m-function others will call, and anything with a number after it is just a helper for the main bit. He groups all of these together under one letter to show that they work together to offer some functionality. Its sort of like saying: 300 | 301 | ```go 302 | // Exposed functionality 303 | func F(a, b, c) { 304 | f1(a, b, c) 305 | f2(a, b, c) 306 | } 307 | 308 | // private helper 309 | func f1(a, b, c) { 310 | // ... 311 | } 312 | 313 | // private helper 314 | func f2(a, b, c) { 315 | // ... 316 | } 317 | ``` 318 | 319 | In the paper `f` specifically stands for "find". It will go to the first `e` in the tape (the beginning of the tape), and then begin to move rightward. If it finds the desired character (`a`), it moves to the m-configuration `C`. If it cannot find `a` before it hits two blank squares in a row it will move to m-configuration `B`. Our implementation of `f` can be found at the top of [abbreviated.go](./abbreviated.go). 320 | 321 | ### Example 3 - Functions within functions 322 | ```go 323 | // This table moves to the right, printing 0 twice in a row, and continuing on infinitely. 324 | // Example: " 00 00 00" 325 | MConfigurations{ 326 | { 327 | Name: "b", 328 | Symbols: []string{"*"}, 329 | Operations: []string{"R"}, 330 | // Take note here, the first move to `f` will have params `f(b, 0)`, and `0`. 331 | // The second move to `f` will have params `b` and `0`. 332 | FinalMConfiguration: "f(f(b, 0), 0)", 333 | }, 334 | { 335 | Name: "f(A, a)", 336 | Symbols: []string{"*"}, 337 | Operations: []string{"Pa", "R"}, // Prints `a`, and moves to the right 338 | FinalMConfiguration: "A", // Moves to the m-configuration provided as parameter `A` 339 | }, 340 | } 341 | 342 | // compiles to: 343 | 344 | MConfigurations{ 345 | { 346 | Name: "q1", 347 | Symbols: []string{"*"}, 348 | Operations: []string{"R"}, 349 | FinalMConfiguration: "q2", // First move to `f` 350 | }, 351 | { 352 | Name: "q2", 353 | Symbols: []string{"*"}, 354 | Operations: []string{"P0", "R"}, 355 | FinalMConfiguration: "q3", // Second move to `f` 356 | }, 357 | { 358 | Name: "q3", 359 | Symbols: []string{"*"}, 360 | Operations: []string{"P0", "R"}, 361 | FinalMConfiguration: "q1", // Moves back to `b` 362 | }, 363 | } 364 | ``` 365 | 366 | ### Example 4 - Symbol parameters 367 | This one was fun. If there is a symbol (in the Symbol column) of our abbreviated table, but it is not a symbol the machine prints and also not a parameter of our m-function, it is a "symbol parameter". We basically "read" the symbol, and pass whatever it was as a parameter. By convention I will always prepend symbol parameters with an underscore (`_`) for clarity. 368 | 369 | ```go 370 | // This table moves to the right, copying the symbol it was just looking at (infinitely). 371 | // Assume the table is only capable of printing `0`` and `1`. 372 | MConfigurations{ 373 | { 374 | Name: "b", 375 | Symbols: []string{"_y"}, // Our "symbol parameter" 376 | Operations: []string{"R"}, 377 | FinalMConfiguration: "f(_y)", // Pass the symbol parameter to `f` 378 | }, 379 | { 380 | Name: "f(a)", 381 | Symbols: []string{"*"}, 382 | Operations: []string{"Pa", "R"}, // where it is simply printed 383 | FinalMConfiguration: "b", 384 | }, 385 | } 386 | 387 | // compiles to: 388 | 389 | MConfigurations{ 390 | { 391 | Name: "q1", 392 | Symbols: []string{"0"}, // We must enumerate this m-function once for each possible symbol 393 | Operations: []string{"R"}, 394 | FinalMConfiguration: "q2", 395 | }, 396 | { 397 | Name: "q1", 398 | Symbols: []string{"1"}, // Here is the other possible symbol 399 | Operations: []string{"R"}, 400 | FinalMConfiguration: "q2", 401 | }, 402 | { 403 | Name: "q2", 404 | Symbols: []string{"*"}, 405 | Operations: []string{"P0"}, // The version of `f` that prints `0` 406 | FinalMConfiguration: "q1", 407 | }, 408 | { 409 | Name: "q2", 410 | Symbols: []string{"*"}, 411 | Operations: []string{"P1"}, // The version of `f` that prints `0` 412 | FinalMConfiguration: "q1", 413 | }, 414 | } 415 | ``` 416 | 417 | Throughout the rest of the section, Turing gives a bunch of m-functions which he will use later in [section 7](./GUIDE.md#section-7---detailed-description-of-the-universal-machine). These are enumerated with comments at the top of [abbreviated.go](./abbreviated.go) and are tested in [abbreviated_tests.go](./abbreviated_test.go). Most of them are helpers for copying, erasing, printing, etc. 418 | 419 | ## Section 5 - Enumeration of computable sequences 420 | 421 | In this section Turing tells us how to "standardize" his tables. This standardization occurs in three steps: 422 | 423 | - **Standard Table**: Modify the m-configurations (which could include adding new ones) such that there is only one Symbol, one Print operation, and one Move operation. 424 | - **Standard Description**: Take these m-configurations, and convert them to one long string. 425 | - **Description Number**: Convert our Standard Description string into a number. 426 | 427 | Performing these standardizations allow us to do some interesting things. The first is to put the Standard Description on a Tape and compute on it (the Standard Description is just a string of symbols), which is explored in [section 6](./GUIDE.md#section-6---the-universal-computing-machine). Another is to treat a Description Number just like any other number (which allows to treat Machines like numbers) - this is explored in [section 8](./GUIDE.md#section-8---application-of-the-diagonal-process). 428 | 429 | ### Implementation Details 430 | 431 | This entire section is quite readable, and its implementation can be found in [standard.go](./standard.go), and tested in [standard_test](./standard_test.go). Here is how it works: 432 | ```go 433 | standardTable := NewStandardTable(MachineInput{ 434 | MConfigurations: []MConfiguration{ 435 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 436 | {"c", []string{" "}, []string{"R"}, "e"}, 437 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 438 | {"k", []string{" "}, []string{"R"}, "b"}, 439 | }, 440 | PossibleSymbols: []string{"0", "1"}, // Required so we can convert `*`, `!x`, etc. 441 | }) 442 | 443 | // Can be used just like any other MachineInput 444 | machineInput := standardTable.MachineInput 445 | machine := NewMachine(machineInput) 446 | machine.Move(50) 447 | 448 | // Capable for converting the resulting Tape from running this Machine back to the original symbols 449 | symbolMap := standardTable.SymbolMap 450 | fmt.Println(symbolMap.TranslateTape(machine.Tape())) 451 | 452 | // Turing's Standard Description 453 | fmt.Println(standardTable.StandardDescription) 454 | 455 | // Turing's Description Number 456 | fmt.Println(standardTable.DescriptionNumber) 457 | ``` 458 | 459 | ## Section 6 - The universal computing machine 460 | 461 | This section, and the [next section](./GUIDE.md#section-7---detailed-description-of-the-universal-machine) are dedicated to explaining and implementing Turing's "universal computing machine". Turing explains that `U` is a Machine that is capable of computing the same sequence as any other Machine `M`, provided the Standard Description of `M` is supplied as the Tape of `U`. 462 | 463 | ### The first programmable machine 464 | It is worth stopping to think about this for a minute. Turing is saying we can create a table of m-configurations that take a Machine as input and reproduce that same Machine's output. As far as I am aware, this is the first conceptualization of software in the modern sense. With `U`, we no longer have to configure Machines directly via m-configurations any more (analogous to hardward). We simply print out our desired Machine on a Tape and feed it into `U` (in other words, we just "program" `U`). 465 | 466 | ### Outline of `U` 467 | 468 | Turing now gives an overview of how he can achieve `U` by breaking the problem into subparts. The first is to build a machine `M'` that will print the complete configuration of `M` on the `F`-squares. The complete configuration is a full history of the moves of a machine. He suggests that we encode the complete configuration in the same standard form as the Standard Description itself. 469 | 470 | Even if we were to accomplish `M'`, we would need to somehow locate `M`'s Tape output somewhere. Turing suggests that this output be interweaved between complete configurations between colons, like this: 471 | ```go 472 | " ... : 0 : ... : 1 : ... " 473 | ``` 474 | 475 | So we don't actually get the *exact* output of `M` (there will be giant complete configurations between the actual computed sequence), but `U` would indeed "compute" the same sequence as `M`. 476 | 477 | What follows in the next section is fascinating - all on paper Turing builds `U`, arguably the first computer. 478 | 479 | ## Section 7 - Detailed description of the universal machine 480 | 481 | Here Turing gives the full table of m-configurations and m-functions for `U`, relying on the helper m-functions he created in [section 4](./GUIDE.md#section-4---abbreviated-tables). Here is an outline of the actual working of `U`: 482 | 483 | 1. `U` takes a Tape starting with `ee`, and then `M`'s Standard Description on the `F`-squares, and finally a double-colon (`::`). 484 | 2. `b`: `U` starts by finding `::`, printing `: D A`, and moving to m-configuration `anf`. You can think of `b` as initialization/setup, and `anf` as the start of `U`'s main loop. 485 | 3. `anf`: Find the last complete configuration, and mark out the following: 486 | 1. The m-configuration name (somewhere within the complete configuration) 487 | 2. The symbol directly to the right (which is the scanned square). 488 | 3. This marking is done by printing `y` to the right of these squares. 489 | 4. `kom`: Moving leftwards, find the m-configuration within the Standard Description that matches that marked by `y`. This is done by: 490 | 1. Finding a semi-colon (representing the start of an m-configuration) 491 | 2. Marking the first two sections (m-configuration name and symbol) with `x`. 492 | 3. Comparing all squares marked with `y` with those marked with `x` (using `kmp`). 493 | 4. If they don't match, mark the semi-colon with `z`, and start again (this time skipping all semi-colons marked `z`). 494 | 5. If they *do* match, we found our m-configuration, move on to `sim`. 495 | 6. In both cases all `x`'s and `y`'s are erased. 496 | 5. `sim`: This step does three things: 497 | 1. Mark the symbol within the Print operation with `u`. 498 | 2. Mark the final m-configuration with `y`. 499 | 3. Move on to `mk`. 500 | 6. `mk`: Returns to the complete configuration, and divides into four sections via markers: 501 | 1. `x`: The symbol directly to the left of the m-configuration name. 502 | 2. `v`: Every symbol before `x`. 503 | 3. ` `: Skip over the m-configuration name, and the scanned symbol. 504 | 4. `w`: Everything to the right of the scanned symbol. 505 | 5. Finally, we move onto `sh` 506 | 7. `sh`: This is the section that prints the actual symbol from `M` (if applicable). We do this by: 507 | 1. Check if the last square scanned (`u`), is blank. 508 | 2. If it is indeed blank, we are writing a new character in the sequence for `M`. Print that character after the complete configuration between two colons (as described in [section 6](./GUIDE.md#section-6---the-universal-computing-machine)). 509 | 3. Move on to `inst` 510 | 8. `inst`: Finally, write the next complete configuration. 511 | 1. We already have all of the relevant sections marked out with `v`, `y`, `x`, `u`, and `w`. We just need to stitch them together. 512 | 2. This is done depending on if our Move operation is `L`, `R`, or `N`. 513 | 1. All three options below print characters marked by `v` first, and `w` last. 514 | 2. `L`: `y`, then `x`, then `u`. 515 | 3. `R`: `x`, then `u`, then `y`. 516 | 4. `N`: `x`, then `y`, then `u`. 517 | 3. After we print the complete configuration in the correct order, start again (go back to `anf`, which is out #3.) 518 | 519 | Note that there are at least 4 small bugs in Turing's original paper in this section. Fortunately, Petzold compiled a list of fixes for them (originally spotted by [Davies](https://en.wikipedia.org/wiki/Donald_Davies) and [Post](https://en.wikipedia.org/wiki/Emil_Leon_Post)). 520 | 521 | ### Implementation Details 522 | 523 | Our implementation is in [universal.go](./universal.go) and tested in [universal_test.go](./universal_test.go). Here is the interface: 524 | 525 | ```go 526 | machineInput := MachineInput{ 527 | MConfigurations: []MConfiguration{ 528 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 529 | {"c", []string{" "}, []string{"R"}, "e"}, 530 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 531 | {"k", []string{" "}, []string{"R"}, "b"}, 532 | }, 533 | } 534 | 535 | standardTable := NewStandardTable(machineInput) 536 | 537 | univeralMachineInput := NewUniversalMachine(UniversalMachineInput{ 538 | StandardDescription: st.StandardDescription, 539 | SymbolMap: st.SymbolMap, 540 | }) 541 | universalMachine := NewMachine(univeralMachineInput) 542 | universalMachine.MoveN(500000) 543 | 544 | // Should be the same as if we just created a Machine from machineInput 545 | fmt.Println(universalMachine.TapeStringFromUniversalMachine()) 546 | ``` 547 | 548 | ## Section 8 - Application of the diagonal process 549 | 550 | This section returns to the question of whether the computable numbers are enumerable, and the nuances that determine the difference between computable numbers and real numbers. 551 | 552 | ### Cantor's diagonal argument 553 | 554 | This entire section relies on the reader understanding the general idea of [Cantor's diagonal argument](https://en.wikipedia.org/wiki/Cantor%27s_diagonal_argument). The gist of the argument is quite intuitive, and I think after watching a quick video (I recommend [Numberphile's](https://www.youtube.com/watch?v=elvOZm0d4H0)) you should be up-to-speed. 555 | 556 | ### An incorrect application of the diagonal process 557 | 558 | In the first paragraph Turing notes that one may think the computable numbers are not enumerable for the same reason (Cantor's diagonal argument) as the real numbers. The next sentence tripped me up: 559 | 560 | > It might, for instance, be thought that the limit of a sequence of computable numbers must be computable. This is clearly only true if the sequence of computable numbers is defined by some rule. 561 | 562 | Turing is simply saying that computable numbers have an extra property (they must actually be calculable by finite means), so you wouldn't be able to do necessarily do things that you would do to the real numbers, and expect another computable number to result. You might be able to, but you might need some additional rules on top to ensure you arrive at a computable number. 563 | 564 | To show this, Turing now fallaciously applies the diagonal argument to computable numbers. He first assumes the computable sequences are enumerable, and calculates the "sequence on the diagonal" which he calls `b` (he uses the lowercase Greek letter beta). Given that `b` is computable, he uses some math (the actual math doesn't matter) to prove that `b` cannot exist (and therefore computable sequences are not enumerable). 565 | 566 | Turing points out that the reason this argument doesn't hold is because of the assumption that `b` is computable. The reason we thought `b` was computable in the first place is because we "computed" it from the enumeration of computable sequences. But referring back to Turing's definiton of computable (calculable by finite means), this would necessarily mean that the process of enumeration would have to be finite. The next few paragraphs are dedicated to proving that this process of enumerating computable sequences cannot be done in a finite number of steps. 567 | 568 | ### Does `D` exist? 569 | 570 | To prove that we can't enumerate computable sequences by finite means, Turing uses a proof by contradition. He assumes we *can* enumerate computable sequences by finite means, and finds a paradox. 571 | 572 | I will make a small note here on the phrase `circle-free`. The wording is unclear which is unfortunate, but its worth digging at what Turing is getting at. By `circle`, Turing is referring to some "bug" that prevents a machine from making progress in printing its sequence. A `circular` machine is one that will never make progress (or may even halt) when attempting to print its sequence. `circle-free` machines *eventually* make progress. 573 | 574 | Back to the proof. Turing supposes the existence of two machines `D` and `H`: 575 | 576 | - `D` - When supplied with an S.D., it will print `s` (for satisfactory) if the S.D. represents a machine that is circle-free, or `u` (for unsatisfactory) if the S.D. represents a machine is circular. 577 | - It is assumed that any scratch work of `D` will be erased. 578 | - Note that in order to be satisfactory, the machine must also be well-defined (the S.D. actually represents a functioning machine). 579 | - `D` is a part of `H`. 580 | - The inner workings of `D` are not provided by Turing. 581 | - `H` - Relying on `D` and `U`, `H` will print `b'` (the "sequence on the diagonal", without the modification by $1$ to make it unique). `H` does this by: 582 | - Start `N` at the Description Number $1$, and at each step increment ($2$, $3$, ...). 583 | - Keep a running counter `R` for what place we are at in the diagonal. 584 | - At each step, do the following: 585 | - Provide the S.D. of `N` to `D`. If it is satisfactory (`s`), increment `R`. We say that `R` at the `N`th step is `R(N)`. 586 | - For example, if `N` is the first circle-free machine found, then `R(N)` is $1$. 587 | - `H` then must find the `R(N)`th digit of the sequence for the machine described by `N`, which is the `R(N)`th digit of `b'`. 588 | 589 | Here is a table outlining the steps of `H` (with thanks to Charles Petzold): 590 | 591 | | N | well-defined? | circle-free? | R(N) | `b'` so far | `b` so far | note | 592 | | ------------- | ------------- | ------------ | ---- | ----------- | ---------- | --------- | 593 | | 1 | No | N/A | 0 | N/A | N/A | | 594 | | 2 | No | N/A | 0 | N/A | N/A | | 595 | | ... | | | | | | | 596 | | 313,325,317 | Yes | Yes | 1 | 1 | 0 | prints 1s | 597 | | ... | | | | | | | 598 | | 3,133,255,317 | Yes | Yes | 2 | 10 | 01 | prints 0s | 599 | | ... | | | | | | | 600 | 601 | 602 | If you found the description of `H` is complicated, here is a more intuitive way to think about it: `H` just loops over all natural numbers, converts the number to an S.D., tests if the S.D. is circle-free, and then finds the appropriate digit to add to the sequence on the diagonal. If it is still too complicated, I have implemented `H` (the parts that are possible) in [diagonal.go](./diagonal.go) and [diagonal_test.go](./diagonal_test.go). I think its worth understanding `H` in full before we get to the proof by contradition. 603 | 604 | Given this description, if `H` in its entirety (including `D`) is circle-free, then the computable numbers are obviously enumerable by finite means. For his proof, let us assume that `H` is indeed circle-free. 605 | 606 | Before he gives the proof, Turing explains that the non-`D` parts of `H` are clearly circle-free (and in fact in [diagonal_test.go](./diagonal_test.go) we proved this). This makes sense because it basically just loops over the natural numbers, outsources the hard part to `D`, keeps track of the count of circle-free machines (`R`), grabs the `R`th digit of a sequence and prints it. Turing does this because he is gearing up to accuse some part of `H` of not being circle-free, and he doesn't want the blame to fall on these simple parts of `H`. 607 | 608 | Now comes the proof. Turing supposes that over the course of `H` looping over the Description Numbers of all machines, it will have to (at some point) arrive at the Description Number of `H` itself (which he says is `K`). Its fascinating to think of what happens at this step. `D` will have to determine if `H` is satisfactory (circle-free) or unsatisfactory. `H` can't come back unsatisfactory, because we just assumed `H` is circle-free. At the same time, it cannot be satisfactory - when trying to calculate the `R(K)`th digit in our diagonal sequence, we must go through the entirety of `H`'s steps once more, and in fact we have to do this recursion infinitely. There is an infinite loop, or in Turing's terms, `H` is circular (but we just assumed it was satisfactory, or circle free). `H` paradoxically cannot be satisfactory or unsatisfactory, so `D` cannot exist. 609 | 610 | With this proof, Turing shows: 611 | 1. No machine exists that can determine whether another machine is circle-free 612 | 2. Diagonalization cannot be applied to computable numbers 613 | 3. Computable numbers *are* theoretically enumerable in the sense that we can: 614 | 1. Enumerate the natural numbers 615 | 2. Use them to represent machines 616 | 3. Possibly compute a sequence using that machine, **however**... 617 | 4. ...you cannot actually perform this enumeration by finite means! 618 | 619 | To answer our original question on enumerability of computable numbers: it depends on our definition of enumerability. My opinion is that to be enumerable, someone must be able to actually perform the enumeration, and therefore they are **not** enumerable. 620 | 621 | ### Does `E` exist? 622 | 623 | Turing now employs a similar proof by contradiction, this time for a machine `E` which he will prove useful later. He explains the goal of the proof: 624 | 625 | > there can be no machine E which, when supplied with the S.D of an arbitrary machine M, will determine whether M ever prints a given symbol (0 say). 626 | 627 | He first supposes a machine `M1` which prints the same sequence as `M` except for it replaces the first printed 0 with a 0̄. Similarly `M2` replaces the first two printed 0's with 0̄'s. These machines are quite easy to implement and are contained in [diagonal.go](./diagonal.go) and [diagonal_test.go](./diagonal_test.go). 628 | 629 | Next comes the machine `F` which prints the S.D. of `M1`, `M2`, ..., etc. successively. Turing now supposes the machine `G` which combines `F` and `E`. It uses `F` to loop over `M1`, `M2`, etc., and then uses `E` to test if 0 is ever printed. For each step in the loop, if it is found that a 0 is never printed, then `G` itself will print a 0. 630 | 631 | Now Turing turns this in on itself and has `E` test `G`. Because `G` only prints 0 when there is no 0 printed by `M`, we can tell if `M` prints 0 infinitely often by checking if `G` never prints a 0. Now we have a way to determine if 0 is printed infinitely often by `M`. It should be clear that we can similarly determine if 1 is printed infinitely often using the same tactic. 632 | 633 | Here is the crux of the proof - the problem is that if we have a way of determining if 0's and 1's are printed infinitely often, then we can tell if a machine is circle-free. We already know that this is impossible from our result in the sub-section above. Therefore, `E` cannot exist. 634 | 635 | ### There is a general process for determining... 636 | 637 | The final paragraph in this section is a small aside that leads us into the next section, and makes a connection between Turing's machine and mathematical logic. The first part explains that we can't assume that "there is a general process for determining ..." means that "there is a machine for determining ...". We have to prove that our machines can truly do anything that human computers can do first. 638 | 639 | He gives us a preview of how he will do this: he says that each of these "general process" problems can be broken down into a mathematical logic problem. The sequence computed by a machine can be the answer to the logic problem "is `G(n)` true or false" where `n` is any integer and `G` is any logical function. For example: 640 | 641 | | Computed Sequence | Logical Function | 642 | | ----------------- | ------------------------- | 643 | | `10000000000000...` | `IsZero(n)` | 644 | | `01000000000000...` | `IsOne(n)` | 645 | | `10101010101010...` | `IsEven(n)` | 646 | | `01010101010101...` | `IsOdd(n)` | 647 | | `10010010010010...` | `IsDivisibleByThree(n)` | 648 | | `10010010010010...` | `IsDivisibleByThree(n)` | 649 | 650 | Turing uses this connection between his machines and logic for the rest of the paper. With this insight he can now attempt to create a process (as in *decision process*) that computes any logical function. 651 | 652 | ## Section 9 - The extent of computable numbers 653 | 654 | Until now, Turing has not explained the reasoning behind his definition of "computable". Here is an excerpt from section 1: 655 | 656 | > We have said that the computable numbers are those whose decimals are calculable by finite means. This requires rather more explicit definition. No real attempt will be made to justify the definitions given until we reach § 9. For the present I shall only say that the justification lies in the fact that the human memory is necessarily limited. 657 | 658 | Turing now will now give some arguments that describe the "extent" of the computable numbers. By being more philosophically rigorous he will be able to build on top of these arguments, which gives him an angle from which to attack the decision problem later. 659 | 660 | The secret to understanding section 9 is this: Turing wants to prove that his machine is capable of "computing" anything that a human (or anything else) is capable of "computing". If Turing can convince the reader of this, he can use his machines in proofs related to mathematical logic without the reader worrying that the proofs don't apply universally. Without this section, a reader might object to Turing's proof in the following way: 661 | 662 | "Sure, Turing proved *his machines* are incapable of accomplishing X, but that doesn't necessary prove that X is unaccomplishable..." - Someone who skipped section 9. 663 | 664 | Turing attempts to convince the reader using three separate arguments. Before diving into the arguments, he gives us a preview of how he will build upon this foundation: 665 | 666 | > Once it is granted that computable numbers are all "computable", several other propositions of the same character follow. In particular, it follows that, if there is a general process for determining whether a formula of the Hilbert function calculus is provable, then the determination can be carried out by a machine. 667 | 668 | Here he is simply saying that if the answer to Hilbert's decision problem is `yes` (that there is an algorithm that can decide if a logic statement is **provable** from a set of axioms, for every possible statement), then it should be possible to construct a machine that carries out this decision process. 669 | 670 | ### Argument `a` - A direct appeal to intuition 671 | 672 | The first of Turing's arguments should be easy for the modern reader to understand. Turing is essentially convincing the reader that his machines are "[Turing Complete](https://en.wikipedia.org/wiki/Turing_completeness)" (a term that Turing obviously did not have access to at the time). For us, it is a long-established fact that there is nothing "special" happening in the human brain (there is nothing in the brain that is not replicatable by a machine that is powerful enough). 673 | 674 | In Turing's time this was not the case, and he went into painstaking detail about why this intuitively true. This argument in its entirety is quite readable, so I won't add anything else here except that when Turing mentions "computer" he is referring to a human performing computations. 675 | 676 | ### Argument `b` - A proof of the equivalence of two definitions 677 | 678 | Turing's second argument is more complex, and will require an understanding of [first-order logic](https://en.wikipedia.org/wiki/First-order_logic), and specifically [Hilbert's calculus](https://en.wikipedia.org/wiki/Hilbert_system). I won't explain these in detail, but here is a crash course, specifically in Hilbert's (and Turing's) notation: 679 | 680 | #### First-order logic 101 681 | 682 | - Propositions are sentences that can be true or false 683 | - Ex: It will snow today 684 | - Repesented by capital letters ($X$, $Y$, etc.) 685 | - $\vee$ represents OR 686 | - Ex: $X \vee Y$ is true if $X$ or $Y$ is true 687 | - $\\&$ represents AND 688 | - Ex: $X \\& Y$ is true if both $X$ and $Y$ are true 689 | - $-$ represents NOT 690 | - Ex: $-X$ means $X$ is not true 691 | - $→$ represents implication 692 | - $X → Y$ is equal to $-X \vee Y$ 693 | - $\sim$ represents equality 694 | - $X \sim Y$ is equal to $(X → Y) \\& (Y → X)$ 695 | - Parentheses $()$ denote evaluation order 696 | - Predicates are functions over natural numbers that evaluate to a truth value 697 | - Ex: $\text{IsPrime}(x)$ where $\text{IsPrime}(4)$ is false and $\text{IsPrime}(5)$ is true 698 | - $\exists$ represents existential quantification 699 | - Ex: $(\exists x)\text{IsPrime}(x)$ means there exists a natural number that is prime. 700 | - $(\exists x)\text{B}(x)$ is equal to $\text{B}(0) \vee \text{B}(1) \vee \text{B}(2) ...$ if $x$ represents natural numbers 701 | - $(x)$ represents universal quantification 702 | - Ex: $(x)\text{IsPrime}(x)$ means that all natural numbers are prime (which is of course false) 703 | - $(x)\text{B}(x)$ is equal to $B(0) \\& B(1) \\& B(2) ...$ if $x$ represents natural numbers 704 | 705 | Turing wants to use a version of Hilbert's calculus that is modified slightly (he wants to use a finite set of natural numbers, etc.). This is just so he can prove that his machine that simulates first-order logic is finite, and therefore whatever is calculated is a "computable number" 706 | 707 | #### Equivalence 708 | 709 | Back to argument `b`. This argument has the following outline: 710 | 1. Turing's machines are capable of representing and simulating Hilbert's calculus (the two definitions are "equivalent"). 711 | 2. Numbers defined by Hilbert's calculus in the way Turing describes include *all* computable numbers (just simulate the correct first-order logic statement). 712 | 3. Therefore, Turing's machine can be directly applied to problems relating to Hilbert's calculus (like the decision problem). 713 | 714 | Turing shows (1) and (2), while (3) is implied. Lets get into the details of the argument. 715 | 716 | #### `K` exists 717 | 718 | Turing first presupposes that a machine `K` exists (it actually does this time...) that can find all provable statements of Hilbert's calculus. We know that `K` exists because we can recursively enumerate all statements from a set of axioms, a consequence of [Gödel's completeness theorum](https://en.wikipedia.org/wiki/G%C3%B6del%27s_completeness_theorem). 719 | 720 | How does this actually work though? `K` is implemented in [hilbert.go](./hilbert.go) and [hilbert_test.go](./hilbert_test.go) to show you, but the secret is to just brute force every combination of operators and axioms using prefix notation. 721 | 722 | #### Peano Axioms 723 | 724 | Turing chooses these axioms to be [Peano axioms](https://en.wikipedia.org/wiki/Peano_axioms), which Turing defines as $P$, or: 725 | 726 | $$(\exists u)N(u) \\;\\; \\& \\;\\; (x)(N(x) → (\exists y)F(x, y)) \\;\\; \\& \\;\\; (F(x, y) → N(y))$$ 727 | 728 | where: 729 | 730 | - $N(x)$ - $x$ is a natural number 731 | - $F(x, y)$ - The successor function ($y$ is one greater than $x$) 732 | 733 | The equation above essentially means: 734 | 1. $(\exists u)N(u)$ - There exists a natural number. 735 | 2. $(x)(N(x) → (\exists y)F(x, y))$ - For every natural number, there exists a successor to that number. 736 | 3. $F(x, y) → N(y)$ - A successor to a natural number is itself also a natural number. 737 | 738 | You can think of Peano's axioms as a way to bootstrap mathematics within first-order logic. Using Peano's axioms you can do things like actually defining $\text{IsPrime}(x)$ using only first-order logic. 739 | 740 | *Aside: Petzold explains Turing is missing some axioms to ensure the uniqueness of zero, uniqueness of the successor, etc., but lets just assume *$P$* correctly enumerates the Peano axioms.* 741 | 742 | #### Computing a sequence 743 | 744 | Turing now explains that we will be attempting to compute a sequence $a$, and provides some predicates that will help later: 745 | 746 | - $G_a(x)$ - The $x$'th figure of $a$ is $1$ 747 | - $-G_a(x)$ - The $x$'th figure of $a$ is $0$ 748 | 749 | Using $P$ and any other combination of propositions built from the axioms, we can define a statement $U$ (Turing uses the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) letter A which is not available in GitHub's LaTeX) which give the foundation for computing $a$ using first-order logic. Note that these "other combination of propositions" are what makes $a$ unique. 750 | 751 | Next we have two statements $A_n$ and $B_n$, which represent the following: 752 | 753 | - $A_n$ is the statement built up from our axioms that implies that the $n$'th figure of $a$ is $1$. 754 | - $B_n$ is the statement built up from our axioms that implies that the $n$'th figure of $a$ is $0$. 755 | 756 | It should be clear that only either $A_n$ or $B_n$ can be true. Turing uses exactly the amount of successor functions in $A_n$/$B_n$ that are required to ensure that we are keeping things finite. 757 | 758 | Now we have everything we need to describe a machine $K_a$ that can compute $a$: 759 | 760 | - For each motion $n$ of $K_a$ the $n$'th figure of $a$ is computed. 761 | - In motion $n$: 762 | - Write down $A_n$ and $B_n$ on the tape 763 | - Enumerate all theorums deduced from the set of axioms using `K` 764 | - Eventually we will find either $A_n$ or $B_n$ in this enumeration. If it is $A_n$, print $1$. If it is $B_n$, print $0$. 765 | 766 | To recap what is happening here: We have created a machine ($K_a$) that computes the sequence $a$ digit-by-digit. It does this by checking if our custom first-order logic statement ($U$) is true or false for a given digit (via brute force) and printing $1$ or $0$ accordingly. Our implementation of $K_a$ can be found in [hilbert.go](./hilbert.go) and [hilbert_test.go](./hilbert_test.go). 767 | 768 | Turing sews the argument up by explaining that all computable numbers can be derived this way (we would just need the correct statement $U$). He then gives a gentle reminder that these computable numbers are not all definable numbers (which we learned in section 8). 769 | 770 | In the final subsection (III), Turing relates the axiom system described above with the human argument from subsection (I). It's a bit philosophical, but I believe Turing is dispelling doubts readers may have about the human argument from subsection (I). Instead of having to rely on the muddy "state of mind" concept, we can replace it with a more formulaic one as described in subsection (II). 771 | 772 | Argument `c` is left to [section 10](./GUIDE.md#section-10---examples-of-large-classes-of-numbers-which-are-computable) (below). 773 | 774 | ## Section 10 - Examples of large classes of numbers which are computable 775 | 776 | Now Turing expounds a bit about computable functions. I think the key to understanding this section is getting the concept of a computable function to "click". Note that whenever Turing says "integral number" in this section he is referring to a "natural number". 777 | 778 | ### Computable functions of natural numbers 779 | 780 | For the entire paper we have been talking about computing a number, like $5$, $\tfrac{1}{3}$, $\sqrt{2}$, etc. Turing now wants to think about computing functions with defined input and output types. A simple one to think about is a function $f(x)$ that takes one variable ($x$) which is a natural number, and which returns a natural number. Let's use the function (to take Petzold's example) $f(x) = 2x + 1$. The input and output of $f(x) = 2x + 1$ are: 781 | 782 | | Input | Output | 783 | | ----- | ------ | 784 | | 0 | 1 | 785 | | 1 | 3 | 786 | | 2 | 5 | 787 | | 3 | 7 | 788 | 789 | We could codify this function of natural numbers into the following: 790 | 791 | $$010111011111011111110...$$ 792 | 793 | where in between every $0$, is the output of $f(x)$ where $x$ is $0$, $1$, $2$, ..., etc. in $1$'s. 794 | 795 | Turing says that a function whose input and output are natural numbers like this is "a computable function" (in the sense that we can compute the outputs of the function for every possible input). Here is how we can prove this to ourselves: 796 | 797 | We already know from section 9 that we can use a machine to check if a first-order logic equation is true or false (with 1 representing true and 0 representing false). We also have a mechanism (the Peano axioms) to encode our desired function of natural numbers into first-order logic. 798 | 799 | From this it should be clear we can do the following: 800 | 801 | 1. Encode our function of natural numbers into first-order logic via the Peano axioms. 802 | 1. Enumerate (via `K` from above) all possible statements from the set of axioms to see if our statement from (1) is true or false for a given input (we can do this in a machine). 803 | 1. Have a machine print $1$ if true or $0$ if false. Do this progressively for all inputs. 804 | 805 | ### The rest of the section 806 | 807 | Turing goes on in the rest of this section to talk about functions that take a computable number as input and as output (and more). Because these numbers could be infinitely long, we would need to take the input and compute the output at the same time (for the simplest function we would read one digit and output one digit, then move onto the next). 808 | 809 | I'm going to skip the rest of this section in this guide because it is extremely in the weeds and not required for [section 11](./GUIDE.md#section-11---application-to-the-entscheidungsproblem). Feel free to read Petzold's description of whats going on here, but I have to be honest I didn't follow anything after the description of Dedekind's cuts applied to computable numbers. 810 | 811 | Maybe one day I'll come back and finish this section up. 812 | 813 | ## Section 11 - Application to the Entscheidungsproblem 814 | 815 | ### The Entscheidungsproblem 816 | 817 | Okay, the Entscheidungsproblem. It is worth taking some time to discuss what this is and why it is important. Probably worth watching [this Veritasium video](https://www.youtube.com/watch?v=HeQX2HjkcNo) first. 818 | 819 | Long ago [David Hilbert](https://en.wikipedia.org/wiki/David_Hilbert) and [Wilhelm Ackermann](https://en.wikipedia.org/wiki/Wilhelm_Ackermann) posed a question about first-order logic (thank's to Petzold for finding the source): 820 | 821 | > The decision problem is solved when we know a procedure with a finite number of operations that determines the validity or satisfiability of any given expression. 822 | 823 | > The decision problem must be considered the main problem of mathematical logic. 824 | 825 | Hilbert and Ackermann envisioned an algorithm that could universally prove to you whether a first-order logic statement was provable (can we *decide* whether it is or is not possible to produce a proof) based on a set of axioms, for any possible statement constructed from those set of axioms (in finite time). They believed it was just a matter of time before we find such an algorithm and show that there was "no such thing as an unsolvable problem". 826 | 827 | So would an algorithm that solves the decision problem look like? It would take, as inputs, any possible first-order logic statement and a set of axioms. The output of the algorithm would be whether or not the statement is provable (and if true, a proof to prove it). 828 | 829 | It is important to note here that Turing is going to substitute "process" or "procedure" or "algorithm" here with his machines (he earned the right to do this in section 9). If the Entscheidungsproblem is looking for a "process", Turing's machine will be the thing carrying out that process. 830 | 831 | Note that this notion of decidability is slightly different from Gödel's Incompleteness Theorum which proved that first-order logic is not complete. 832 | 833 | - **Completeness**: 834 | - For every statement $S$, can we prove that $S$ is either true or false? 835 | - To be complete, there must be an algorithm $Prove(S)$ that returns true or false, along with a proof for all statements. 836 | - Gödel gave an example of an unprovable statement. 837 | - **Decidability**: 838 | - For every statement $S$, can we decide if $S$ is provable or not? 839 | - To be decidable, there must be an algorithm $Decide(S)$ that returns true or false depending on if $Prove(S)$ has an answer, for all statements. 840 | - Turing gave an example of an undecidable statement. 841 | 842 | ### The Proof 843 | 844 | Turing's approach in the proof below is another proof by contradiction. The gist is: 845 | 846 | 1. Turing will construct a machine that encodes a first-order logic statement $Un(M)$. 847 | 1. The statement $Un(M)$ essentially states $0$ is printed on the tape somewhere in $M$'s complete configurations. 848 | 1. Turing shows (via two Lemmas) that $Un(M)$ is provable *if and only if* $0$ is printed on the tape (using [logical equivalence](https://en.wikipedia.org/wiki/Logical_equivalence)). 849 | 1. We assume the Entscheidungsproblem has a solution. Then we would have a process for determining (deciding) whether $Un(M)$ is provable. 850 | 1. Because of our logical equivalence, this would mean we would have a process for determining if a machine ever prints a $0$ on the tape. 851 | 1. We know from section 8 that a process for determining if a machine ever prints a $0$ does not exist, therefore there is no process for determining if $Un(M)$ is provable, and the Entscheidungsproblem cannot have a general solution (a solution for all statements). 852 | 1. We know from sections 9 and 10 that Turing's machines encompass all computation, so Turing's answer to the Entscheidungsproblem is universal (there isn't some other type of machine where we may be able to answer the Entscheidungsproblem in the affirmative). 853 | 854 | ### $Un(M)$ 855 | 856 | After framing the proof, Turing builds up his description of $Un(M)$. He takes a bottom-up approach, which I will replicate here. He first describes the following three helper functions: 857 | 858 | - $R_{S_l}(x, y)$ - means "In the $x$'th complete configuration of $M$, the symbol on the $y$'th square is $S_l$" (where $S_0$ is the blank square, $S_1$ is $0$, $S_2$ is $1$, and so on). 859 | - $I(x, y)$ - means "In the $x$'th complete configuration of $M$, the $y$'th square is the one that is scanned" (in each complete configuration, we scan one square). 860 | - $K_{q_m}(x)$ - means "In the $x$'th complete configuration of $M$, the m-configuration is $q_m$". 861 | 862 | These are building blocks Turing will use to construct a statement about a machine $M$ itself. He gives one more helper $F(x, y)$ which is the successor function, before getting to $Inst$, which stands for "instruction". 863 | 864 | Turing now wants to encode the m-configurations of $M$ one-by-one in formal logic. Each standard instruction (in it's "move operation") can move left ($L$), right ($R$), or do nothing ($N$). Using his helper functions, he first gives us the formulation for an instruction that moves left, which I break down below: 865 | 866 | - $Inst \{q_i S_j S_k L q_l\}$ - This format should be familiar to you (it is Turing's standard form). We have the m-configuration name $q_i$, the scanned symbol $S_j$, the printed symbol $S_k$, the move operation (left in this case), and the final m-configuration $q_l$. 867 | - $(x, y, x', y')$ - Four universal quantifiers begin the statement. $x$ and $x'$ represent the $x$'th and $x+1$'th m-configurations. $y$ and $y'$ represent the $y$'th and the $y-1$'th square. 868 | - $R_{S_j}(x, y) \\& I(x, y) \\& K_{q_i}(x) \\& F(x, x') \\& F(y', y)$ - this part (everything before the $→$ sign) encodes the initial state of the m-configuration: It is the $x$'th complete configuration, and in the complete configuration $x$ we state the m-configuration name, the location of the scanned square, and the scanned symbol. We also codify that $x'$ is the complete configuration that comes after $x$, and $y$ is the square that comes after $y'$. 869 | - $I(x', y') \\& R_{S_k}(x', y) \\& K_{q_l}(x)$ - the second line describes what should happen in the next complete configuration ($x'$). This should make intuitive sense - the complete configuration $x$ should indeed imply what occurs in complete configuration $x'$. 870 | - The last line of $Inst$ is incorrect, and Turing published a correction a year later. I won't go over the details, but it essentially states that all other squares on the tape remain the same. 871 | 872 | Turing doesn't go into the detail of the equations for instructions that move right ($R$) or do nothing ($N$) (and neither will we), but it should be simple given his formulation of an instruction that moves left ($L$). 873 | 874 | Turing now wants to take each row in our m-configuration, convert it to an instruction of the form $Inst$, and make one large formula $Des(M)$: 875 | 876 | $$Inst \\; \\& \\; Inst \\; \\& \\; Inst \\; \\& \\; ...$$ 877 | 878 | $Des(M)$ is an abbreviation describes the entirety of $M$. Turing can now finally construct his $Un(M)$, which can be thought of basically stating "some machine $M$ prints $0$ at some point". $Un(M)$ is quite a large equation. First lets break down the less-important bits: 879 | 880 | - $N(x)$ - The helper function stating that $x$ is a natural number. 881 | - $(x)(N(x) → (\exists x')F(x, x'))$ - All numbers have succesors. 882 | - $(y, z)(F(y, z) → N(y) \\& N(z))$ - All successors are also natural numbers. 883 | 884 | And here is the meat of the equation: 885 | 886 | - $(\exists u)$ - $Un(M)$ starts with the existence of some number which Turing intends to be zero. I think Turing's ordering is a little strange here, but he uses $u$ to set the initial state of the machine: 887 | - $K_{q_1}(u)$ - in the first complete configuration the m-configuration is $q_1$ 888 | - $(y)R_{S_0}(u, y)$ - in the first complete configuration all squares are blank 889 | - $I(u, u)$ - in the first complete configuration the tape starts at the first square 890 | - $Des(M)$ - Turing adds the full description of the machine $M$. In total, the left-hand side of the $→$ sign comes to mean "the motion of $M$". We have the description of $M$, the initial state of the $M$, and successors to encode motion itself. 891 | - $(\exists s)(\exists t)[N(s) \\& N(t) \\& R_{S_1}(s, t)]$ - For some complete configuration $s$ of $M$, on some square $t$, there exists a printed character $0$. 892 | - Turing abbreviates (for use in Lemma 1) the left-hand side of the equation to $A(M)$. 893 | 894 | Turing concludes this bit by summarizing: 895 | 896 | > $Un(M)$ has the interpretation, "in some complete configuration of $M$, $S_1$ (i.e. $0$) appears on the tape" 897 | 898 | Turing now uses two Lemmas to show that $Un(M)$ is provable *if and only if* $0$ appears on the tape in some complete configuration of $M$. Each lemma deals with one side of the logical equivalence. 899 | 900 | ### Lemma 1 901 | 902 | > LEMMA 1. If $S_1$ appears on the tape in some complete configuration of $M$, then $Un(M)$ is provable. 903 | 904 | This is the harder of the two Lemmas. Given the assumption that $0$ appears somewhere on the tape in some complete configuration of $M$, we need to show that $Un(M)$ is provable. 905 | 906 | This one is pretty involved. I won't go into full detail (I recommend Petzold's explanations for this), but my interpretation of Lemma 1 is: 907 | 908 | 1. The initial state of the machine, the description of the machine, and the fact that natural numbers have successors ($A(M) \\& F^{(n)}$) prove the first complete configuration ($CC_0$). 909 | 1. Each complete configuration proves it's successor ($CF_n → CF_{n+1}$). 910 | 1. All complete configurations are therefore provable. 911 | 1. The lemma takes for granted that in some complete configuration, $0$ is somewhere on the tape ($CC_N → R_{S_1}(u^{(N)}, u^{(K)})$ where $u^{(x)}$ equals the number $x$, and $K$ is some number). 912 | 1. We can therefore show that $Un(M)$ is provable - the initial state of the machine, the description of the machine, and the successor functions imply (as long as we have our assumption) that $0$ is on the tape in one of the complete configurations -$(\exists u)A(M) → (\exists s)(\exists t)R_{S_1}(s, t)$. 913 | 914 | If you think about it, it makes sense. Of course we would be able to show $Un(M)$ is provable if we already know that $0$ is printed somewhere. 915 | 916 | ### Lemma 2 917 | 918 | > LEMMA 2. If $Un(M)$ is provable, then $S_1$ appears on the tape in some complete configuration of $M$. 919 | 920 | This Lemma is much easier. If we assume $Un(M)$ is provable, then it should result from our formulation of $Un(M)$ that $0$ appears on the tape. No tricks or gotchas here. 921 | 922 | ### Conclusion 923 | 924 | Turing now has the right to say that "$Un(M)$ is provable *if and only if* $0$ appears on the tape in some complete configuration of $M$". I will simply restate Turing's short answer to the Entscheidungsproblem: 925 | 926 | > We are now in a position to show that the Entscheidungsproblem cannot be solved. Let us suppose the contrary. Then there is a general (mechanical) process for determining whether $Un(M)$ is provable. By Lemmas 1 and 2, this implies that there is a process for determining whether $M$ ever prints $0$, and this is impossible, by § 8. Hence the Entscheidungsproblem cannot be solved. 927 | 928 | Let's bring this full circle (no pun intended). We can actually use our implementation to construct a machine that simulates $Un(M)$ for some $M$: 929 | 930 | ```go 931 | m := MachineInput{ ... } // Any machine 932 | undecidableMachine := NewUndecidableMachine(m) // Prints `1` or `0` if `m` does or does not ever print a `0`. 933 | 934 | for { 935 | undecidableMachine.Move() 936 | if strings.Contains(undecidableMachine.TapeStringFromUniversalMachine(), "1") { 937 | // The machine `m` prints a 0 at some point (this block is possible) 938 | } else { 939 | // The machine `m` never prints a 0 (this block is not logically possile) 940 | } 941 | } 942 | ``` 943 | 944 | If by some stroke of luck $M$ actually prints a $0$, we may actually be able to detect this as $Un(M)$ runs through the motion of $M$. The problem is that we cannot actually detect whether some machine $M$ prints a $0$ (other than simulating the machine in-full, which requires infinite time). So we don't have a process in the general case that returns an answer here, which can be seen in the `else` block above. 945 | 946 | The "Undecidable Machine" (which doesn't actually work), is available in [decision.go](./decision.go) and [decision_test.go](./decision_test.go). 947 | 948 | The final bit of the paper outside of the appendix talks about putting $Un(M)$ into [prenex normal form](https://en.wikipedia.org/wiki/Prenex_normal_form). Turing tells us that $Un(M)$ is a member of the [reduction class](https://en.wikipedia.org/wiki/Reduction_(complexity)) $\forall \exists \forall \exists^6$ (meaning all statements in this class are also undecidable). I found it interesting that particular reduction classes are indeed decidable (though of course Turing just proved that in the general case first-order logic is not decidable). 949 | 950 | ## Appendix - Computability and effective calculability 951 | 952 | I have decided to learn about the Lambda (λ) Calculus and λ-definability in a separate project - https://github.com/planetlambert/lambda. 953 | 954 | When this project is complete, I will return here and do the following: 955 | 1. Flesh out this section of the guide 956 | 1. Implement Turing's machine which is capable of parsing and converting λ-definable sequences in [lambda.go](./lambda.go) and [lambda_test.go](./lambda_test.go) 957 | 958 | ## Overall Thoughts 959 | 960 | - Turing essentially invents (in theory) the computer and the concept of software/programming, all in service to solve a (what I thought was an obscure, but I guess is actually an important) math problem. 961 | - It felt as if Turing piled three or four genius insights on-top of one another, so while reading I was never able to get comfortable before he took things to the next level. 962 | - There are a lot of bugs (which makes sense as he was not able to run the machine himself). 963 | - Attempting to learn this paper as a non-mathematician makes me want to get a deeper understanding of the history of math/logic (probably via [Frege to Gödel](https://www.amazon.com/Frege-Godel-Mathematical-1879-1931-Sciences/dp/0674324498)) 964 | - The appendix is a great segue to Church's [Lambda Calculus](https://en.wikipedia.org/wiki/Lambda_calculus), which will probably be my next project. 965 | - [Self-reference](https://en.wikipedia.org/wiki/Self-reference) is a fascinating concept. I hear [Douglas Hofstadter](https://en.wikipedia.org/wiki/Douglas_Hofstadter)'s books are the best resource on this topic. 966 | - It is interesting that the modern formulation of the [halting problem](https://en.wikipedia.org/wiki/Halting_problem) is nowhere to be found in this paper. There seem to be many research offshoots that stem from Turing. 967 | - Someone on [HN](https://news.ycombinator.com/item?id=37548095) asked about [Busy Beavers](https://en.wikipedia.org/wiki/Busy_beaver) so I implemented them in [beaver.go](./beaver.go) and [beaver_test.go](./beaver_test.go). 968 | - The concept I struggled with the most is the difference between [decidability](https://en.wikipedia.org/wiki/Decidability_(logic)) and [completeness](https://en.wikipedia.org/wiki/Completeness_(logic)). -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Justin Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Turing 2 | 3 | The first complete and open source implementation of Alan Turing's famous 1936 paper - [On Computable Numbers, with an Application to the Entscheidungsproblem](https://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf). 4 | 5 | ## Why? 6 | 7 | I attempted to read Turing's paper, and found it too difficult to understand. I figured reading a reference implementation (that includes `m-functions`, a working `univeral machine`, etc.) would make things clearer, but was surprised to find that a full implementation does not exist on the internet. I thought it could be fun to try to write my own. 8 | 9 | ## Guide 10 | For those who intend to read the full paper I recommend starting with [The Annotated Turing](https://www.amazon.com/Annotated-Turing-Through-Historic-Computability/dp/0470229055) by Charles Petzold (which explains the paper line-by-line with annotations). 11 | 12 | I wrote a section-by-section guide ([GUIDE.md](./GUIDE.md)) that explains the paper in the context of this codebase. My hope is that the combination of a reference implementation and walkthrough helps others get value from the paper like I did. 13 | 14 | ### Progress 15 | - [X] [**Introduction**](./GUIDE.md#introduction) 16 | - [X] [**Section 1** - Computing machines](./GUIDE.md#section-1---computing-machines) 17 | - [X] [**Section 2** - Definitions](./GUIDE.md#section-2---definitions) 18 | - [X] [**Section 3** - Examples of computing machines](./GUIDE.md#section-3---examples-of-computing-machines) 19 | - [X] [**Section 4** - Abbreviated tables](./GUIDE.md#section-4---abbreviated-tables) 20 | - [X] [**Section 5** - Enumeration of computable sequences](./GUIDE.md#section-5---enumeration-of-computable-sequences) 21 | - [X] [**Section 6** - The universal computing machine](./GUIDE.md#section-6---the-universal-computing-machine) 22 | - [X] [**Section 7** - Detailed description of the universal machine](./GUIDE.md#section-7---detailed-description-of-the-universal-machine) 23 | - [X] [**Section 8** - Application of the diagonal process](./GUIDE.md#section-8---application-of-the-diagonal-process) 24 | - [X] [**Section 9** - The extent of computable numbers](./GUIDE.md#section-9---the-extent-of-computable-numbers) 25 | - [X] [**Section 10** - Examples of large classes of numbers which are computable](./GUIDE.md#section-10---examples-of-large-classes-of-numbers-which-are-computable) 26 | - [X] [**Section 11** - Application to the Entscheidungsproblem](./GUIDE.md#section-11---application-to-the-entscheidungsproblem) 27 | - [X] [**Appendix** - Computability and effective calculability](./GUIDE.md#appendix---computability-and-effective-calculability) 28 | 29 | ## Usage 30 | 31 | For those who want to use the implementation, here is how to get started: 32 | 33 | ```shell 34 | go get github.com/planetlambert/turing@latest 35 | ``` 36 | 37 | ```go 38 | import ( 39 | "fmt" 40 | 41 | "github.com/planetlambert/turing" 42 | ) 43 | 44 | func main() { 45 | // Input for Turing Machine that prints 0 and 1 infinitely 46 | machineInput := turing.MachineInput{ 47 | MConfigurations: turing.MConfigurations{ 48 | {Name: "b", Symbols: []string{" "}, Operations: []string{"P0", "R"}, FinalMConfiguration: "c"}, 49 | {Name: "c", Symbols: []string{" "}, Operations: []string{"R"}, FinalMConfiguration: "e"}, 50 | {Name: "e", Symbols: []string{" "}, Operations: []string{"P1", "R"}, FinalMConfiguration: "k"}, 51 | {Name: "k", Symbols: []string{" "}, Operations: []string{"R"}, FinalMConfiguration: "b"}, 52 | }, 53 | } 54 | 55 | // Construct the Turing Machine and move 50 times 56 | machine := turing.NewMachine(machineInput) 57 | machine.MoveN(50) 58 | 59 | // Prints "0 1 0 1 0 1 ..." 60 | fmt.Println(machine.TapeString()) 61 | 62 | // Convert the same Machine input to Turing's "standard" forms 63 | standardTable := turing.NewStandardTable(machineInput) 64 | standardMachineInput := standardTable.MachineInput 65 | symbolMap := standardTable.SymbolMap 66 | standardDescription := standardTable.StandardDescription 67 | descriptionNumber := standardTable.DescriptionNumber 68 | 69 | // Also prints "0 1 0 1 0 1 ..." 70 | standardMachine := turing.NewMachine(standardMachineInput) 71 | standardMachine.MoveN(50) 72 | fmt.Println(machine.TapeString()) 73 | 74 | // Prints ";DADDCRDAA;DAADDRDAAA;DAAADDCCRDAAAA;DAAAADDRDA" 75 | fmt.Println(standardDescription) 76 | 77 | // Prints "73133253117311335311173111332253111173111133531" 78 | fmt.Println(descriptionNumber) 79 | 80 | // Construct Turing's Universal Machine using the original Machine's Standard Description (S.D.) 81 | universalMachine := turing.NewMachine(turing.NewUniversalMachine(turing.UniversalMachineInput{ 82 | StandardDescription: standardDescription, 83 | SymbolMap: symbolMap, 84 | })) 85 | 86 | // Turing's Universal Machine is quite complex and has to undergo quite a few moves to achieve the same Tape 87 | universalMachine.MoveN(500000) 88 | 89 | // Prints "0 1 0 1 0 1 ..." 90 | fmt.Println(universalMachine.TapeStringFromUniversalMachine()) 91 | } 92 | ``` 93 | 94 | [Go Package Documentation here.](https://pkg.go.dev/github.com/planetlambert/turing) 95 | 96 | ## Testing 97 | 98 | ```shell 99 | go test ./... 100 | ``` 101 | 102 | ## FAQ 103 | - Why Go? 104 | - I like Go and I think its easy to read. 105 | - How is the performance? 106 | - My goal for this repository is to be a learning resource, so when possible I bias towards readability. 107 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | 1. Finish implementation of `H` and `G` machines in [diagonal.go](./diagonal.go) and [diagonal_test.go](./diagonal_test.go) 4 | 1. Requires refactoring the Universal Machine to use custom sentinel values (`:` and `::`) 5 | 1. Thought: Chain machines together using Go rather than using Machine Tape 6 | 1. Finish implementation of `K` and `Ka`machines in [hilbert.go](./hilbert.go) and [hilbert_test.go](./hilbert_test.go) 7 | 1. Finish implementation of the impossible undecidable machine in [decision.go](./decision.go) and [decision_test.go](./decision_test.go). 8 | 1. After the `lambda` project, flesh out section 11 and finish implementation of the Lambda machine in [lambda.go](./lambda.go) and [lambda_test.go](./lambda_test.go). 9 | 1. Flesh out more of section 10 -------------------------------------------------------------------------------- /abbreviated.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "slices" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // The m-configurations (or rather, m-functions) below are helper 10 | // functions to be used eventually in Turing's Universal Machine 11 | var ( 12 | // From the m-configuration `f` the machine finds the 13 | // symbol of form `a` which is farthest to the left (the "first a") 14 | // and the m-configuration then becomes `C`. If there is no `a` 15 | // then the m-configuration becomes `B`. 16 | findLeftMost = []MConfiguration{ 17 | {"f(C, B, a)", []string{"e"}, []string{"L"}, "f1(C, B, a)"}, 18 | {"f(C, B, a)", []string{"!e", " "}, []string{"L"}, "f(C, B, a)"}, 19 | {"f1(C, B, a)", []string{"a"}, []string{}, "C"}, 20 | {"f1(C, B, a)", []string{"!a"}, []string{"R"}, "f1(C, B, a)"}, 21 | {"f1(C, B, a)", []string{" "}, []string{"R"}, "f2(C, B, a)"}, 22 | {"f2(C, B, a)", []string{"a"}, []string{}, "C"}, 23 | {"f2(C, B, a)", []string{"!a"}, []string{"R"}, "f1(C, B, a)"}, 24 | {"f2(C, B, a)", []string{" "}, []string{"R"}, "B"}, 25 | } 26 | 27 | // From `e(C, B, a)` the first `a` is erased and -> `C`. 28 | // If there is no `a` -> `B`. 29 | // From `e(B, a)` all letters `a` are erased and -> `B`. 30 | erase = []MConfiguration{ 31 | {"e(C, B, a)", []string{"*", " "}, []string{}, "f(e1(C, B, a), B, a)"}, 32 | {"e1(C, B, a)", []string{"*", " "}, []string{"E"}, "C"}, 33 | {"e(B, a)", []string{"*", " "}, []string{}, "e(e(B, a), B, a)"}, 34 | } 35 | 36 | // From `pe(C, b)` the machine prints `b` at the end of the sequence 37 | // of symbols and -> `C` 38 | printAtTheEnd = []MConfiguration{ 39 | {"pe(C, b)", []string{"*", " "}, []string{}, "f(pe1(C, b), C, e)"}, 40 | {"pe1(C, b)", []string{"*"}, []string{"R", "R"}, "pe1(C, b)"}, 41 | {"pe1(C, b)", []string{" "}, []string{"Pb"}, "C"}, 42 | } 43 | 44 | // From `fl(C, B, a)` it does the same as for `f(C, B, a)`, 45 | // but moves to the left before -> `C` 46 | findLeft = []MConfiguration{ 47 | {"l(C)", []string{"*", " "}, []string{"L"}, "C"}, 48 | {"fl(C, B, a)", []string{"*", " "}, []string{}, "f(l(C), B, a)"}, 49 | } 50 | 51 | // From `fr(C, B, a)` it does the same as for `f(C, B, a)`, 52 | // but moves to the right before -> `C` 53 | findRight = []MConfiguration{ 54 | {"r(C)", []string{"*", " "}, []string{"R"}, "C"}, 55 | {"fr(C, B, a)", []string{"*", " "}, []string{}, "f(r(C), B, a)"}, 56 | } 57 | 58 | // `c(C, B, a)`. The machine writes at the end the first symbol 59 | // marked `a` and -> `C` 60 | copy = []MConfiguration{ 61 | {"c(C, B, a)", []string{"*", " "}, []string{}, "fl(c1(C), B, a)"}, 62 | {"c1(C)", []string{"_b"}, []string{}, "pe(C, _b)"}, 63 | } 64 | 65 | // `ce(B, a)`. The machine copies down in order at the end 66 | // all symbols marked `a` and erases the letters `a` -> `B` 67 | copyAndErase = []MConfiguration{ 68 | {"ce(C, B, a)", []string{"*", " "}, []string{}, "c(e(C, B, a), B, a)"}, 69 | {"ce(B, a)", []string{"*", " "}, []string{}, "ce(ce(B, a), B, a)"}, 70 | } 71 | 72 | // `re(C, B, a, b)`. The machine replaces the first `a` by `b` and 73 | // -> `C` (-> `B` if there is no `a`). 74 | // `re(B, a, b)`. The machine replaces all letters `a` by `b` -> `B` 75 | replace = []MConfiguration{ 76 | {"re(C, B, a, b)", []string{"*", " "}, []string{}, "f(re1(C, B, a, b), b, a)"}, 77 | {"re1(C, B, a, b)", []string{"*", " "}, []string{"E", "Pb"}, "C"}, 78 | {"re(B, a, b)", []string{"*", " "}, []string{}, "re(re(B, a, b), B, a, b)"}, 79 | } 80 | 81 | // `cr(B, a)` differs from `ce(B, a)` only in that the letters `a` are not erased. 82 | // The m-configuration `cr(B, a)` is taken up when no letters `b` are on the tape. 83 | copyAndReplace = []MConfiguration{ 84 | {"cr(C, B, a, b)", []string{"*", " "}, []string{}, "c(re(C, B, a, b), B, a)"}, 85 | {"cr(B, a, b)", []string{"*", " "}, []string{}, "cr(cr(B, a, b), re(B, a, b), a, b)"}, 86 | } 87 | 88 | // The first symbol marked `a` and the first marked `b` are compared. 89 | // If there is neither `a` nor `b` -> E. If there are both and the symbols are alike, 90 | // -> `C`. Otherwise -> `A`. 91 | compare = []MConfiguration{ 92 | {"cp(C, A, E, a, b)", []string{"*", " "}, []string{}, "fl(cp1(C, A, b), f(A, E, b), a)"}, 93 | {"cp1(C, A, b)", []string{"_y"}, []string{}, "fl(cp2(C, A, _y), A, b)"}, 94 | {"cp2(C, A, y)", []string{"y"}, []string{}, "C"}, 95 | {"cp2(C, A, y)", []string{"!y", " "}, []string{}, "A"}, 96 | } 97 | 98 | // `cpe(C, A, E, a, b)` differs from `cp(C, A, E, a, b)` in that in the case when there is 99 | // a similarity the first `a` and `b` are erased. 100 | // `cpe(A, E, a, b)`. The sequence of symbols marked `a` is compared with the sequence 101 | // marked `b`. -> `C` if they are similar. Otherwise -> `A`. Some of the symbols `a` and `b` are erased. 102 | compareAndErase = []MConfiguration{ 103 | {"cpe(C, A, E, a, b)", []string{"*", " "}, []string{}, "cp(e(e(C, C, b), C, a), A, E, a, b)"}, 104 | {"cpe(A, E, a, b)", []string{"*", " "}, []string{}, "cpe(cpe(A, E, a, b), A, E, a, b)"}, 105 | } 106 | 107 | // `g(C, a)`. The machine finds the last symbol of form `a` -> `C`. 108 | findRightMost = []MConfiguration{ 109 | {"g(C)", []string{"*"}, []string{"R"}, "g(C)"}, 110 | {"g(C)", []string{" "}, []string{"R"}, "g1(C)"}, 111 | {"g1(C)", []string{"*"}, []string{"R"}, "g(C)"}, 112 | {"g1(C)", []string{" "}, []string{}, "C"}, 113 | {"g(C, a)", []string{"*", " "}, []string{}, "g(g1(C, a))"}, 114 | {"g1(C, a)", []string{"a"}, []string{}, "C"}, 115 | {"g1(C, a)", []string{"!a", " "}, []string{"L"}, "g1(C, a)"}, 116 | } 117 | 118 | // `pe2(C, a, b)`. The machine prints `a b` at the end. 119 | printAtTheEnd2 = []MConfiguration{ 120 | {"pe2(C, a, b)", []string{"*", " "}, []string{}, "pe(pe(C, b), a)"}, 121 | } 122 | 123 | // `ce3(B, a, b, y)`. The machine copies down at the end first the symbols 124 | // marked `a` then those marked `b`, and finally those marked `y`. 125 | // It erases the symbols `a`, `b`, `y`. 126 | copyAndErase2 = []MConfiguration{ 127 | {"ce2(B, a, b)", []string{"*", " "}, []string{}, "ce(ce(B, b), a)"}, 128 | {"ce3(B, a, b, y)", []string{"*", " "}, []string{}, "ce(ce2(B, b, y), a)"}, 129 | {"ce4(B, a, b, y, z)", []string{"*", " "}, []string{}, "ce(ce3(B, b, y, z), a)"}, 130 | {"ce5(B, a, b, y, z, w)", []string{"*", " "}, []string{}, "ce(ce4(B, b, y, z, w), a)"}, 131 | } 132 | 133 | // From `e(C)` the marks are erased from all marked symbols -> `C` 134 | eraseAll = []MConfiguration{ 135 | {"e(C)", []string{"e"}, []string{"R"}, "e1(C)"}, 136 | {"e(C)", []string{"!e", " "}, []string{"L"}, "e(C)"}, 137 | {"e1(C)", []string{"*"}, []string{"R", "E", "R"}, "e1(C)"}, 138 | {"e1(C)", []string{" "}, []string{}, "C"}, 139 | } 140 | ) 141 | 142 | // Returns all helper functions 143 | func allhelperFunctions() []MConfiguration { 144 | helperFunctions := []MConfiguration{} 145 | helperFunctions = append(helperFunctions, findLeftMost...) 146 | helperFunctions = append(helperFunctions, erase...) 147 | helperFunctions = append(helperFunctions, printAtTheEnd...) 148 | helperFunctions = append(helperFunctions, findLeft...) 149 | helperFunctions = append(helperFunctions, findRight...) 150 | helperFunctions = append(helperFunctions, copy...) 151 | helperFunctions = append(helperFunctions, copyAndErase...) 152 | helperFunctions = append(helperFunctions, replace...) 153 | helperFunctions = append(helperFunctions, copyAndReplace...) 154 | helperFunctions = append(helperFunctions, compare...) 155 | helperFunctions = append(helperFunctions, compareAndErase...) 156 | helperFunctions = append(helperFunctions, findRightMost...) 157 | helperFunctions = append(helperFunctions, printAtTheEnd2...) 158 | helperFunctions = append(helperFunctions, printAtTheEnd2...) 159 | helperFunctions = append(helperFunctions, copyAndErase2...) 160 | helperFunctions = append(helperFunctions, eraseAll...) 161 | return helperFunctions 162 | } 163 | 164 | // Input for an Abbreviated Table 165 | type AbbreviatedTableInput MachineInput 166 | 167 | // Gives MachineInput for the abbreviated table. This requires "compiling" the abbreviated table. 168 | func NewAbbreviatedTable(input AbbreviatedTableInput) MachineInput { 169 | at := &abbreviatedTable{ 170 | input: input, 171 | } 172 | 173 | return at.toMachineInput() 174 | } 175 | 176 | // Helper struct to compile the abbreviated table 177 | type abbreviatedTable struct { 178 | input AbbreviatedTableInput 179 | mConfigurationCount int 180 | newMConfigurationNames map[string]string 181 | wasAlreadyInterpretedMap map[string]bool 182 | newMConfigurations []MConfiguration 183 | } 184 | 185 | // Used when parsing m-functions 186 | const ( 187 | functionOpen string = "(" 188 | functionClose string = ")" 189 | functionParamDelimiter string = "," 190 | ) 191 | 192 | // Converts an AbbreviatedTable to a valid Machine, which will contain no skeleton tables 193 | func (at *abbreviatedTable) toMachineInput() MachineInput { 194 | // For each m-configuration that is not an m-function, begin interpreting 195 | for _, mConfiguration := range at.input.MConfigurations { 196 | if !strings.Contains(mConfiguration.Name, functionOpen) { 197 | at.interpretMFunction(mConfiguration.Name, []string{}) 198 | } 199 | } 200 | 201 | var startingMConfiguration string 202 | if len(at.input.StartingMConfiguration) != 0 { 203 | startingMConfiguration = at.newMConfigurationName(at.input.StartingMConfiguration, []string{}) 204 | } 205 | 206 | return MachineInput{ 207 | MConfigurations: at.sortedNewMConfigurations(), 208 | Tape: at.input.Tape, 209 | StartingMConfiguration: startingMConfiguration, 210 | PossibleSymbols: at.input.PossibleSymbols, 211 | NoneSymbol: at.input.NoneSymbol, 212 | Debug: at.input.Debug, 213 | } 214 | } 215 | 216 | // Given an m-function call in the form `f(a, b, x(y, z))`, interpret recursively 217 | func (at *abbreviatedTable) interpretMFunction(name string, params []string) string { 218 | // Standardize m-configuration names 219 | newMConfigurationName := at.newMConfigurationName(name, params) 220 | 221 | // For each m-function call signature, we only need to interpret once 222 | if at.wasAlreadyInterpreted(name, params) { 223 | return newMConfigurationName 224 | } else { 225 | at.markAsInterpreted(name, params) 226 | } 227 | 228 | // For each m-function that matches our name and param length, recursively interpret 229 | for _, mFunction := range at.findMFunctions(name, len(params)) { 230 | // Retrieve the m-function's parameter names 231 | _, mFunctionParams := parseMFunction(mFunction.Name) 232 | 233 | // This bit only required to support the scenario Turing outlines in his `c1` (copy) m-function 234 | // In this scenario the supplied symbol is read and used as a parameter for operations or the 235 | // final m-configuration. 236 | symbolValues := []string{} 237 | symbolParam, isSymbolParam := at.isSymbolParam(mFunction.Symbols, mFunctionParams) 238 | if isSymbolParam { 239 | for _, possibleSymbol := range append(at.input.PossibleSymbols, none) { 240 | symbolValues = append(symbolValues, possibleSymbol) 241 | } 242 | } else { 243 | symbolValues = append(symbolValues, "") 244 | } 245 | 246 | for _, symbolValue := range symbolValues { 247 | // Create a substitution map from parameter names to the provided parameter values 248 | if isSymbolParam { 249 | mFunctionParams = append(mFunctionParams, symbolParam) 250 | params = append(params, symbolValue) 251 | } 252 | substitutionMap := createSubstitutionMap(mFunctionParams, params) 253 | 254 | // Parse the final m-configuration (it may be a function) 255 | finalMFunctionName, finalMFunctionParams := parseMFunction(mFunction.FinalMConfiguration) 256 | 257 | // Perform substitutions on both the final m-configuration name and params 258 | substitutedFinalMFunctionName := at.substituteFinalMConfigurationName(finalMFunctionName, substitutionMap) 259 | substitutedFinalMFunctionParams := at.substituteFinalMConfigurationParams(finalMFunctionParams, substitutionMap) 260 | 261 | // This block recursively attempts to interpret whatever the final m-configuration is (potentially an m-function to follow) 262 | var newFinalMConfigurationName string 263 | if len(substitutedFinalMFunctionParams) == 0 { 264 | // If there were no params, we still might have substituted to an m-function 265 | // If this is the case, we want to parse out the name and params 266 | substitutedFinalMFunctionNameParsedName, substitutedFinalMFunctionNameParsedParams := parseMFunction(substitutedFinalMFunctionName) 267 | newFinalMConfigurationName = at.interpretMFunction(substitutedFinalMFunctionNameParsedName, substitutedFinalMFunctionNameParsedParams) 268 | } else { 269 | // If there were params, go ahead and use those 270 | newFinalMConfigurationName = at.interpretMFunction(substitutedFinalMFunctionName, substitutedFinalMFunctionParams) 271 | } 272 | 273 | // Substitute Symbols and Save m-configuration 274 | at.saveMConfiguration(MConfiguration{ 275 | Name: newMConfigurationName, 276 | Symbols: at.substituteSymbols(mFunction.Symbols, substitutionMap), 277 | Operations: at.substituteOperations(mFunction.Operations, substitutionMap), 278 | FinalMConfiguration: newFinalMConfigurationName, 279 | }) 280 | } 281 | } 282 | 283 | // Bubble up the Standardized m-configuration name 284 | return newMConfigurationName 285 | } 286 | 287 | // Finds all m-functions whose definition matches the name and number of params 288 | func (at *abbreviatedTable) findMFunctions(name string, numParams int) []MConfiguration { 289 | mFunctions := []MConfiguration{} 290 | for _, mFunction := range at.input.MConfigurations { 291 | mFunctionName, mFunctionParams := parseMFunction(mFunction.Name) 292 | if name == mFunctionName && numParams == len(mFunctionParams) { 293 | mFunctions = append(mFunctions, mFunction) 294 | } 295 | } 296 | return mFunctions 297 | } 298 | 299 | func (at *abbreviatedTable) isSymbolParam(symbols []string, mFunctionParams []string) (string, bool) { 300 | if len(symbols) != 1 { 301 | return "", false 302 | } 303 | symbol := symbols[0] 304 | if strings.Contains(symbol, not) || strings.Contains(symbol, any) { 305 | return "", false 306 | } 307 | notAPossibleSymbol := !slices.Contains(append(at.input.PossibleSymbols, none), symbol) 308 | notAMFunctionParam := !slices.Contains(mFunctionParams, symbol) 309 | if notAPossibleSymbol && notAMFunctionParam { 310 | return symbol, true 311 | } 312 | return "", false 313 | } 314 | 315 | // For the Symbols column of an m-function, substitute any m-function params with values 316 | func (at *abbreviatedTable) substituteSymbols(mFunctionSymbols []string, substitutions map[string]string) []string { 317 | substitutedSymbols := []string{} 318 | for _, mFunctionSymbol := range mFunctionSymbols { 319 | if strings.Contains(mFunctionSymbol, not) { 320 | if substitutedSymbol, ok := substitutions[mFunctionSymbol[1:]]; ok { 321 | substitutedSymbols = append(substitutedSymbols, not+substitutedSymbol) 322 | } else { 323 | substitutedSymbols = append(substitutedSymbols, mFunctionSymbol) 324 | } 325 | } else { 326 | if substitutedSymbol, ok := substitutions[mFunctionSymbol]; ok { 327 | substitutedSymbols = append(substitutedSymbols, substitutedSymbol) 328 | } else { 329 | substitutedSymbols = append(substitutedSymbols, mFunctionSymbol) 330 | } 331 | } 332 | } 333 | return substitutedSymbols 334 | } 335 | 336 | // For the Operations of an m-function, substitute any m-function params with values 337 | func (at *abbreviatedTable) substituteOperations(mFunctionOperations []string, substitutions map[string]string) []string { 338 | substitutedOperations := []string{} 339 | for _, mFunctionOperation := range mFunctionOperations { 340 | switch operationCode(mFunctionOperation[0]) { 341 | case printOp: 342 | mFunctionOperationSymbol := string(mFunctionOperation[1]) 343 | if substitutedOperation, ok := substitutions[mFunctionOperationSymbol]; ok { 344 | substitutedOperations = append(substitutedOperations, string(printOp)+substitutedOperation) 345 | 346 | } else { 347 | substitutedOperations = append(substitutedOperations, string(printOp)+mFunctionOperationSymbol) 348 | } 349 | default: 350 | substitutedOperations = append(substitutedOperations, mFunctionOperation) 351 | } 352 | } 353 | return substitutedOperations 354 | } 355 | 356 | // For a parsed final m-configuration column of an m-function, attempt to make a parameter substitution if possible for its name 357 | func (at *abbreviatedTable) substituteFinalMConfigurationName(mFunctionFinalMConfigurationName string, substitutions map[string]string) string { 358 | if substitutedMFunctionFinalMConfigurationName, ok := substitutions[mFunctionFinalMConfigurationName]; ok { 359 | return substitutedMFunctionFinalMConfigurationName 360 | } 361 | return mFunctionFinalMConfigurationName 362 | } 363 | 364 | // For a parsed final m-configuration column of an m-function, attempt to make a parameter substitution if possible for its values 365 | func (at *abbreviatedTable) substituteFinalMConfigurationParams(mFunctionFinalMConfigurationParams []string, substitutions map[string]string) []string { 366 | substitutedMFunctionFinalMConfigurationParams := []string{} 367 | for _, mFunctionFinalMConfigurationParam := range mFunctionFinalMConfigurationParams { 368 | potentialInnerName, potentialInnerParams := parseMFunction(mFunctionFinalMConfigurationParam) 369 | if len(potentialInnerParams) == 0 { 370 | substitutedMFunctionFinalMConfigurationParams = append(substitutedMFunctionFinalMConfigurationParams, at.substituteFinalMConfigurationName(potentialInnerName, substitutions)) 371 | } else { 372 | recursiveSubstitution := at.substituteFinalMConfigurationParams(potentialInnerParams, substitutions) 373 | substitutedMFunctionFinalMConfigurationParams = append(substitutedMFunctionFinalMConfigurationParams, composeMFunction(potentialInnerName, recursiveSubstitution)) 374 | } 375 | } 376 | return substitutedMFunctionFinalMConfigurationParams 377 | } 378 | 379 | // Saves a new m-configuration 380 | func (at *abbreviatedTable) saveMConfiguration(mConfiguration MConfiguration) { 381 | if at.newMConfigurations == nil { 382 | at.newMConfigurations = []MConfiguration{} 383 | } 384 | 385 | at.newMConfigurations = append(at.newMConfigurations, mConfiguration) 386 | } 387 | 388 | // Constructs a new unique m-configuration name 389 | func (at *abbreviatedTable) newMConfigurationName(mFunctionName string, mFunctionParams []string) string { 390 | if at.newMConfigurationNames == nil { 391 | at.newMConfigurationNames = map[string]string{} 392 | } 393 | 394 | key := composeMFunction(mFunctionName, mFunctionParams) 395 | 396 | if mConfigurationName, ok := at.newMConfigurationNames[key]; ok { 397 | return mConfigurationName 398 | } 399 | 400 | newName := mConfigurationNamePrefix + strconv.Itoa(at.mConfigurationCount) 401 | at.mConfigurationCount++ 402 | at.newMConfigurationNames[key] = newName 403 | return newName 404 | } 405 | 406 | // Returns true if this m-function signature was already interpreted 407 | func (at *abbreviatedTable) wasAlreadyInterpreted(mFunctionName string, mFunctionParams []string) bool { 408 | if at.wasAlreadyInterpretedMap == nil { 409 | at.wasAlreadyInterpretedMap = map[string]bool{} 410 | } 411 | 412 | key := composeMFunction(mFunctionName, mFunctionParams) 413 | 414 | if _, ok := at.wasAlreadyInterpretedMap[key]; ok { 415 | return true 416 | } 417 | return false 418 | } 419 | 420 | // Marks an m-function signature as interpreted 421 | func (at *abbreviatedTable) markAsInterpreted(mFunctionName string, mFunctionParams []string) { 422 | if at.wasAlreadyInterpretedMap == nil { 423 | at.wasAlreadyInterpretedMap = map[string]bool{} 424 | } 425 | 426 | key := composeMFunction(mFunctionName, mFunctionParams) 427 | 428 | at.wasAlreadyInterpretedMap[key] = true 429 | } 430 | 431 | // Returns a sorted slice of the stored interpreted m-configurations 432 | func (at *abbreviatedTable) sortedNewMConfigurations() []MConfiguration { 433 | slices.SortFunc(at.newMConfigurations, func(a, b MConfiguration) int { 434 | aInt, _ := strconv.Atoi(a.Name[1:]) 435 | bInt, _ := strconv.Atoi(b.Name[1:]) 436 | return aInt - bInt 437 | }) 438 | return at.newMConfigurations 439 | } 440 | 441 | // Parses an m-function of the form "f(a, b, x(y, z))" into name "f" and params ["a", "b", "x(y, z)"] 442 | func parseMFunction(mFunction string) (string, []string) { 443 | open := strings.Index(mFunction, functionOpen) 444 | if open < 0 { 445 | return mFunction, []string{} 446 | } 447 | 448 | mFunctionName := mFunction[0:open] 449 | params := []string{} 450 | 451 | var currentParam strings.Builder 452 | var recursiveCount int 453 | for _, char := range mFunction[open+1 : len(mFunction)-1] { 454 | charAsString := string(char) 455 | if recursiveCount > 0 || (charAsString != none && charAsString != functionParamDelimiter) { 456 | currentParam.WriteRune(char) 457 | } 458 | if charAsString == functionOpen { 459 | recursiveCount++ 460 | } 461 | if charAsString == functionClose { 462 | recursiveCount-- 463 | } 464 | if recursiveCount == 0 && charAsString == functionParamDelimiter { 465 | // Handles the scenario where we want to use ` ` (None) as a parameter 466 | if currentParam.Len() == 0 { 467 | currentParam.WriteString(none) 468 | } 469 | params = append(params, currentParam.String()) 470 | currentParam.Reset() 471 | } 472 | } 473 | 474 | // Handles the scenario where we want to use ` ` (None) as a parameter 475 | if currentParam.Len() == 0 { 476 | currentParam.WriteString(none) 477 | } 478 | params = append(params, currentParam.String()) 479 | 480 | return mFunctionName, params 481 | } 482 | 483 | // Composes an m-function of name "f" and params ["a", "b", "x(y, z)"] into the form "f(a, b, x(y, z))" 484 | func composeMFunction(name string, params []string) string { 485 | var mFunction strings.Builder 486 | mFunction.WriteString(name) 487 | if len(params) > 0 { 488 | mFunction.WriteString(functionOpen) 489 | mFunction.WriteString(strings.Join(params, functionParamDelimiter)) 490 | mFunction.WriteString(functionClose) 491 | } 492 | return mFunction.String() 493 | } 494 | 495 | // Zips up two arrays of strings into a map 496 | func createSubstitutionMap(keys []string, values []string) map[string]string { 497 | substitutionMap := map[string]string{} 498 | for i, key := range keys { 499 | substitutionMap[key] = values[i] 500 | } 501 | return substitutionMap 502 | } 503 | -------------------------------------------------------------------------------- /abbreviated_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | // `ph`. Prints the provided character, and halts. 10 | printAndHalt = MConfiguration{"ph(b)", []string{"*", " "}, []string{"Pb"}, "halt"} 11 | // `prh`. Prints the provided character to the right, and halts. 12 | printToRightAndHalt = MConfiguration{"prh(b)", []string{"*", " "}, []string{"R", "Pb"}, "halt"} 13 | ) 14 | 15 | func TestParseMFunctionRecursiveFirst(t *testing.T) { 16 | checkParseMFunction(t, "f(x(y, z), a, b)", "f", []string{"x(y, z)", "a", "b"}) 17 | } 18 | 19 | func TestParseMFunctionRecursiveMiddle(t *testing.T) { 20 | checkParseMFunction(t, "f(a, x(y, z), b)", "f", []string{"a", "x(y, z)", "b"}) 21 | } 22 | 23 | func TestParseMFunctionRecursiveLast(t *testing.T) { 24 | checkParseMFunction(t, "f(a, b, x(y, z))", "f", []string{"a", "b", "x(y, z)"}) 25 | } 26 | 27 | func TestParseMFunctionRecursiveTwice(t *testing.T) { 28 | checkParseMFunction(t, "f(x(y, z), x(y, z), b)", "f", []string{"x(y, z)", "x(y, z)", "b"}) 29 | } 30 | 31 | func TestParseMFunctionBlank(t *testing.T) { 32 | checkParseMFunction(t, "f(x, )", "f", []string{"x", " "}) 33 | } 34 | 35 | func TestParseMFunctionNoFunction(t *testing.T) { 36 | checkParseMFunction(t, "f", "f", []string{}) 37 | } 38 | 39 | func checkParseMFunction(t *testing.T, mFunction string, expectedName string, expectedParams []string) { 40 | actualName, actualParams := parseMFunction(mFunction) 41 | if actualName != expectedName { 42 | t.Errorf("got %s, want %s", actualName, expectedName) 43 | } 44 | if !reflect.DeepEqual(actualParams, expectedParams) { 45 | t.Errorf("got %s, want %s", actualParams, expectedParams) 46 | } 47 | } 48 | 49 | func TestFindLeftMost(t *testing.T) { 50 | mConfigurations := []MConfiguration{ 51 | // Invokes the findLeftMost MFunction (`f`), printing `x` or `y` depending on if `0` is found 52 | {"b", []string{"*", " "}, []string{"R", "R", "R"}, "f(ph(x), ph(y), 0)"}, 53 | } 54 | 55 | mConfigurations = append(mConfigurations, printAndHalt) 56 | mConfigurations = append(mConfigurations, findLeftMost...) 57 | possibleSymbols := []string{"e", "x", "y", "0", "1"} 58 | 59 | t.Run("FindFirstZero", func(t *testing.T) { 60 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 61 | MConfigurations: mConfigurations, 62 | Tape: []string{"e", "e", "1", " ", "1", " ", "0", " ", "0"}, 63 | PossibleSymbols: possibleSymbols, 64 | StartingMConfiguration: "b", 65 | })) 66 | m.MoveN(20) 67 | checkTape(t, m.TapeString(), "ee1 1 x 0") 68 | }) 69 | 70 | t.Run("NoZero", func(t *testing.T) { 71 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 72 | MConfigurations: mConfigurations, 73 | Tape: []string{"e", "e", "1", " ", "1"}, 74 | PossibleSymbols: possibleSymbols, 75 | StartingMConfiguration: "b", 76 | })) 77 | m.MoveN(20) 78 | checkTape(t, m.TapeString(), "ee1 1 y") 79 | }) 80 | } 81 | 82 | func TestErase(t *testing.T) { 83 | // Invokes the erase MFunction (`e`), printing `x` or `y` depending on if `z` is found and erased 84 | eraseOnceTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "e(ph(x), ph(y), z)"} 85 | // Invokes the erase MFunction (`e`), printing `x` or `y` depending on if all `z` symbols are found and erased 86 | eraseAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "e(ph(x), z)"} 87 | 88 | mConfigurations := []MConfiguration{} 89 | mConfigurations = append(mConfigurations, printAndHalt) 90 | mConfigurations = append(mConfigurations, findLeftMost...) 91 | mConfigurations = append(mConfigurations, erase...) 92 | possibleSymbols := []string{"e", "0", "z", "x", "y"} 93 | 94 | t.Run("EraseX", func(t *testing.T) { 95 | mConfigurations := append(mConfigurations, eraseOnceTest) 96 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 97 | MConfigurations: mConfigurations, 98 | Tape: []string{"e", "e", "0", "z", "0", "z"}, 99 | PossibleSymbols: possibleSymbols, 100 | StartingMConfiguration: "b", 101 | })) 102 | m.MoveN(20) 103 | checkTape(t, m.TapeString(), "ee0x0z") 104 | }) 105 | 106 | t.Run("EraseXDoesNotExist", func(t *testing.T) { 107 | mConfigurations := append(mConfigurations, eraseOnceTest) 108 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 109 | MConfigurations: mConfigurations, 110 | Tape: []string{"e", "e"}, 111 | PossibleSymbols: possibleSymbols, 112 | StartingMConfiguration: "b", 113 | })) 114 | m.MoveN(20) 115 | checkTape(t, m.TapeString(), "ee y") 116 | }) 117 | 118 | t.Run("EraseAll", func(t *testing.T) { 119 | mConfigurations := append(mConfigurations, eraseAllTest) 120 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 121 | MConfigurations: mConfigurations, 122 | Tape: []string{"e", "e", "", "z", " ", "z"}, 123 | PossibleSymbols: possibleSymbols, 124 | StartingMConfiguration: "b", 125 | })) 126 | m.MoveN(30) 127 | checkTape(t, m.TapeString(), "ee x") 128 | }) 129 | } 130 | 131 | func TestPrintAtTheEnd(t *testing.T) { 132 | // Invokes the printAtTheEnd MFunction (`pe`), printing `x` at the end of the sequence 133 | printAtTheEndTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "pe(halt, x)"} 134 | 135 | mConfigurations := []MConfiguration{} 136 | mConfigurations = append(mConfigurations, findLeftMost...) 137 | mConfigurations = append(mConfigurations, printAtTheEnd...) 138 | mConfigurations = append(mConfigurations, printAtTheEndTest) 139 | possibleSymbols := []string{"e", "0", "x"} 140 | 141 | t.Run("PrintAtTheEnd", func(t *testing.T) { 142 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 143 | MConfigurations: mConfigurations, 144 | Tape: []string{"e", "e", "0", " ", "0"}, 145 | PossibleSymbols: possibleSymbols, 146 | StartingMConfiguration: "b", 147 | })) 148 | m.MoveN(20) 149 | checkTape(t, m.TapeString(), "ee0 0 x") 150 | }) 151 | } 152 | 153 | func TestFindLeft(t *testing.T) { 154 | // Invokes the left MFunction (`l`), printing an `x` to the left of the current tape location 155 | leftTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R", "R"}, "l(ph(0))"} 156 | // Invokes the findLeft MFunction (`fl`) printing an `x` to the left of the first ocurrence of the symb ol 157 | findLeftTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "fl(ph(x), ph(y), 0)"} 158 | 159 | mConfigurations := []MConfiguration{} 160 | mConfigurations = append(mConfigurations, printAndHalt) 161 | mConfigurations = append(mConfigurations, findLeftMost...) 162 | mConfigurations = append(mConfigurations, findLeft...) 163 | possibleSymbols := []string{"e", "0", "1", "x", "y"} 164 | 165 | t.Run("Left", func(t *testing.T) { 166 | mConfigurations := append(mConfigurations, leftTest) 167 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 168 | MConfigurations: mConfigurations, 169 | Tape: []string{"e", "e"}, 170 | PossibleSymbols: possibleSymbols, 171 | StartingMConfiguration: "b", 172 | })) 173 | m.MoveN(20) 174 | checkTape(t, m.TapeString(), "ee0") 175 | }) 176 | 177 | t.Run("FindLeft", func(t *testing.T) { 178 | mConfigurations := append(mConfigurations, findLeftTest) 179 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 180 | MConfigurations: mConfigurations, 181 | Tape: []string{"e", "e", "1", " ", "1", " ", "0", " ", "0"}, 182 | PossibleSymbols: possibleSymbols, 183 | StartingMConfiguration: "b", 184 | })) 185 | m.MoveN(20) 186 | checkTape(t, m.TapeString(), "ee1 1x0 0") 187 | }) 188 | } 189 | 190 | func TestFindRight(t *testing.T) { 191 | // Invokes the right MFunction (`r`), printing an `x` to the right of the current tape location 192 | rightTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "r(ph(x))"} 193 | // Invokes the findRight MFunction (`fr`) printing an `x` to the right of the first ocurrence of the symb ol 194 | findRightTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "fr(ph(x), ph(y), 0)"} 195 | 196 | mConfigurations := []MConfiguration{} 197 | mConfigurations = append(mConfigurations, printAndHalt) 198 | mConfigurations = append(mConfigurations, findLeftMost...) 199 | mConfigurations = append(mConfigurations, findRight...) 200 | possibleSymbols := []string{"e", "0", "1", "x", "y"} 201 | 202 | t.Run("Right", func(t *testing.T) { 203 | mConfigurations := append(mConfigurations, rightTest) 204 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 205 | MConfigurations: mConfigurations, 206 | Tape: []string{"e", "e", "0"}, 207 | PossibleSymbols: possibleSymbols, 208 | StartingMConfiguration: "b", 209 | })) 210 | m.MoveN(20) 211 | checkTape(t, m.TapeString(), "ee0x") 212 | }) 213 | 214 | t.Run("FindRight", func(t *testing.T) { 215 | mConfigurations := append(mConfigurations, findRightTest) 216 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 217 | MConfigurations: mConfigurations, 218 | Tape: []string{"e", "e", "1", " ", "1", " ", "0", " ", "0"}, 219 | PossibleSymbols: possibleSymbols, 220 | StartingMConfiguration: "b", 221 | })) 222 | m.MoveN(20) 223 | checkTape(t, m.TapeString(), "ee1 1 0x0") 224 | }) 225 | } 226 | 227 | func TestCopy(t *testing.T) { 228 | // Invokes the copy MFunction (`c`), copying the `0` to the left of `x` to the end of the sequence. 229 | copyTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "c(halt, halt, x)"} 230 | 231 | mConfigurations := []MConfiguration{} 232 | mConfigurations = append(mConfigurations, findLeftMost...) 233 | mConfigurations = append(mConfigurations, findLeft...) 234 | mConfigurations = append(mConfigurations, printAtTheEnd...) 235 | mConfigurations = append(mConfigurations, copy...) 236 | possibleSymbols := []string{"e", "0", "x"} 237 | 238 | t.Run("Copy", func(t *testing.T) { 239 | mConfigurations := append(mConfigurations, copyTest) 240 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 241 | MConfigurations: mConfigurations, 242 | Tape: []string{"e", "e", "0", " ", "0", "x"}, 243 | PossibleSymbols: possibleSymbols, 244 | StartingMConfiguration: "b", 245 | })) 246 | m.MoveN(30) 247 | checkTape(t, m.TapeString(), "ee0 0x0") 248 | }) 249 | } 250 | 251 | func TestCopyAndErase(t *testing.T) { 252 | // Invokes the copyAndErase MFunction (`ce`), copying the marked figure and erasing the marker. 253 | copyAndEraseOnceTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "ce(halt, halt, x)"} 254 | // Invokes the copyAndErase MFunction (`ce`), copying all of the marked figures and erasing all of the markers. 255 | copyAndEraseAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "ce(halt, x)"} 256 | 257 | mConfigurations := []MConfiguration{} 258 | mConfigurations = append(mConfigurations, findLeftMost...) 259 | mConfigurations = append(mConfigurations, findLeft...) 260 | mConfigurations = append(mConfigurations, printAtTheEnd...) 261 | mConfigurations = append(mConfigurations, erase...) 262 | mConfigurations = append(mConfigurations, copy...) 263 | mConfigurations = append(mConfigurations, copyAndErase...) 264 | possibleSymbols := []string{"e", "0", "1", "x"} 265 | 266 | t.Run("CopyAndEraseOnce", func(t *testing.T) { 267 | mConfigurations := append(mConfigurations, copyAndEraseOnceTest) 268 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 269 | MConfigurations: mConfigurations, 270 | Tape: []string{"e", "e", "0", " ", "0", "x"}, 271 | PossibleSymbols: possibleSymbols, 272 | StartingMConfiguration: "b", 273 | })) 274 | m.MoveN(50) 275 | checkTape(t, m.TapeString(), "ee0 0 0") 276 | }) 277 | 278 | t.Run("CopyAndEraseAll", func(t *testing.T) { 279 | mConfigurations := append(mConfigurations, copyAndEraseAllTest) 280 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 281 | MConfigurations: mConfigurations, 282 | Tape: []string{"e", "e", "0", " ", "1", "x", "0", "x"}, 283 | PossibleSymbols: possibleSymbols, 284 | StartingMConfiguration: "b", 285 | })) 286 | m.MoveN(100) 287 | checkTape(t, m.TapeString(), "ee0 1 0 1 0") 288 | }) 289 | } 290 | 291 | func TestReplace(t *testing.T) { 292 | // Invokes the replace MFunction (`re`), replacing a marker with another. 293 | replaceOnceTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "re(halt, halt, x, y)"} 294 | // Invokes the replace MFunction (`re`), replace all markers with another. 295 | replaceAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "re(halt, x, y)"} 296 | 297 | mConfigurations := []MConfiguration{} 298 | mConfigurations = append(mConfigurations, findLeftMost...) 299 | mConfigurations = append(mConfigurations, replace...) 300 | possibleSymbols := []string{"e", "0", "x", "y"} 301 | 302 | t.Run("ReplaceOnce", func(t *testing.T) { 303 | mConfigurations := append(mConfigurations, replaceOnceTest) 304 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 305 | MConfigurations: mConfigurations, 306 | Tape: []string{"e", "e", "0", "x", "0", "x"}, 307 | PossibleSymbols: possibleSymbols, 308 | StartingMConfiguration: "b", 309 | })) 310 | m.MoveN(100) 311 | checkTape(t, m.TapeString(), "ee0y0x") 312 | }) 313 | 314 | t.Run("ReplaceAll", func(t *testing.T) { 315 | mConfigurations := append(mConfigurations, replaceAllTest) 316 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 317 | MConfigurations: mConfigurations, 318 | Tape: []string{"e", "e", "0", "x", "0", "x"}, 319 | PossibleSymbols: possibleSymbols, 320 | StartingMConfiguration: "b", 321 | })) 322 | m.MoveN(100) 323 | checkTape(t, m.TapeString(), "ee0y0y") 324 | }) 325 | } 326 | 327 | func TestCopyAndReplace(t *testing.T) { 328 | // Invokes the copyAndReplace MFunction (`cr`), copying the marked figure and replacing the mark. 329 | copyAndReplaceOnceTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cr(halt, halt, x, y)"} 330 | // Invokes the copyAndReplace MFunction (`cr`), copying the marked figures and replacing the marks. 331 | copyAndReplaceAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cr(halt, x, y)"} 332 | 333 | mConfigurations := []MConfiguration{} 334 | mConfigurations = append(mConfigurations, findLeftMost...) 335 | mConfigurations = append(mConfigurations, findLeft...) 336 | mConfigurations = append(mConfigurations, printAtTheEnd...) 337 | mConfigurations = append(mConfigurations, erase...) 338 | mConfigurations = append(mConfigurations, copy...) 339 | mConfigurations = append(mConfigurations, copyAndErase...) 340 | mConfigurations = append(mConfigurations, replace...) 341 | mConfigurations = append(mConfigurations, copyAndReplace...) 342 | possibleSymbols := []string{"e", "0", "1", "x", "y"} 343 | 344 | t.Run("CopyAndReplaceOnce", func(t *testing.T) { 345 | mConfigurations := append(mConfigurations, copyAndReplaceOnceTest) 346 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 347 | MConfigurations: mConfigurations, 348 | Tape: []string{"e", "e", "0", " ", "0", "x"}, 349 | PossibleSymbols: possibleSymbols, 350 | StartingMConfiguration: "b", 351 | })) 352 | m.MoveN(100) 353 | checkTape(t, m.TapeString(), "ee0 0y0") 354 | }) 355 | 356 | t.Run("CopyAndReplaceAll", func(t *testing.T) { 357 | mConfigurations := append(mConfigurations, copyAndReplaceAllTest) 358 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 359 | MConfigurations: mConfigurations, 360 | Tape: []string{"e", "e", "0", " ", "1", "x", "0", "x"}, 361 | PossibleSymbols: possibleSymbols, 362 | StartingMConfiguration: "b", 363 | })) 364 | m.MoveN(100) 365 | checkTape(t, m.TapeString(), "ee0 1y0y1 0") 366 | }) 367 | } 368 | 369 | func TestCompare(t *testing.T) { 370 | // Invokes the compare MFunction (`cp`), printing `z` at the end if neither markers exist. 371 | compareNeitherExistTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cp(halt, halt, pe(halt, z), x, y)"} 372 | // Invokes the compare MFunction (`cp`), printing `z` at the end if the figures are not equal. 373 | compareNotEqualTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cp(halt, pe(halt, z), halt, x, y)"} 374 | // Invokes the compare MFunction (`cp`), printing `z` at the end if the figures are equal. 375 | compareEqualTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cp(pe(halt, z), halt, halt, x, y)"} 376 | 377 | mConfigurations := []MConfiguration{} 378 | mConfigurations = append(mConfigurations, findLeftMost...) 379 | mConfigurations = append(mConfigurations, findLeft...) 380 | mConfigurations = append(mConfigurations, printAtTheEnd...) 381 | mConfigurations = append(mConfigurations, compare...) 382 | possibleSymbols := []string{"e", "0", "1", "x", "y", "z"} 383 | 384 | t.Run("CompareNeitherExist", func(t *testing.T) { 385 | mConfigurations := append(mConfigurations, compareNeitherExistTest) 386 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 387 | MConfigurations: mConfigurations, 388 | Tape: []string{"e", "e", "0", " ", "0"}, 389 | PossibleSymbols: possibleSymbols, 390 | StartingMConfiguration: "b", 391 | })) 392 | m.MoveN(100) 393 | checkTape(t, m.TapeString(), "ee0 0 z") 394 | }) 395 | 396 | t.Run("CompareNotEqual", func(t *testing.T) { 397 | mConfigurations := append(mConfigurations, compareNotEqualTest) 398 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 399 | MConfigurations: mConfigurations, 400 | Tape: []string{"e", "e", "0", "x", "1", "y"}, 401 | PossibleSymbols: possibleSymbols, 402 | StartingMConfiguration: "b", 403 | })) 404 | m.MoveN(100) 405 | checkTape(t, m.TapeString(), "ee0x1yz") 406 | }) 407 | 408 | t.Run("CompareEqual", func(t *testing.T) { 409 | mConfigurations := append(mConfigurations, compareEqualTest) 410 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 411 | MConfigurations: mConfigurations, 412 | Tape: []string{"e", "e", "0", "x", "0", "y"}, 413 | PossibleSymbols: possibleSymbols, 414 | StartingMConfiguration: "b", 415 | })) 416 | m.MoveN(100) 417 | checkTape(t, m.TapeString(), "ee0x0yz") 418 | }) 419 | } 420 | 421 | func TestCompareAndErase(t *testing.T) { 422 | // Invokes the compareAndErase MFunction (`cpe`), erasing the markers if the figures are equal. 423 | compareAndEraseOnceTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cpe(halt, halt, halt, x, y)"} 424 | // Invokes the compareAndErase MFunction (`cpe`), erasing the markers if the sequence of figures are equal. 425 | compareAndEraseAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "cpe(halt, halt, x, y)"} 426 | 427 | mConfigurations := []MConfiguration{} 428 | mConfigurations = append(mConfigurations, findLeftMost...) 429 | mConfigurations = append(mConfigurations, findLeft...) 430 | mConfigurations = append(mConfigurations, printAtTheEnd...) 431 | mConfigurations = append(mConfigurations, compare...) 432 | mConfigurations = append(mConfigurations, erase...) 433 | mConfigurations = append(mConfigurations, compareAndErase...) 434 | possibleSymbols := []string{"e", "0", "1", "x", "y"} 435 | 436 | t.Run("CompareAndEraseOnce", func(t *testing.T) { 437 | mConfigurations := append(mConfigurations, compareAndEraseOnceTest) 438 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 439 | MConfigurations: mConfigurations, 440 | Tape: []string{"e", "e", "0", "x", "0", "y"}, 441 | PossibleSymbols: possibleSymbols, 442 | StartingMConfiguration: "b", 443 | })) 444 | m.MoveN(200) 445 | checkTape(t, m.TapeString(), "ee0 0 ") 446 | }) 447 | 448 | t.Run("CompareAndEraseAll", func(t *testing.T) { 449 | mConfigurations := append(mConfigurations, compareAndEraseAllTest) 450 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 451 | MConfigurations: mConfigurations, 452 | Tape: []string{"e", "e", "0", "x", "1", "x", "0", "y", "1", "y"}, 453 | PossibleSymbols: possibleSymbols, 454 | StartingMConfiguration: "b", 455 | })) 456 | m.MoveN(200) 457 | checkTape(t, m.TapeString(), "ee0 1 0 1 ") 458 | }) 459 | } 460 | 461 | func TestFindRightMost(t *testing.T) { 462 | // Invokes the findRightMost MFunction (`g`) (which finds the end of the tape), and prints. 463 | findEndOfTapeTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "g(ph(x))"} 464 | // Invokes the findRightMost MFunction (`g`), and prints to the right of the found character. 465 | findRightMostTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "g(prh(x), 0)"} 466 | 467 | mConfigurations := []MConfiguration{} 468 | mConfigurations = append(mConfigurations, printAndHalt) 469 | mConfigurations = append(mConfigurations, printToRightAndHalt) 470 | mConfigurations = append(mConfigurations, findRightMost...) 471 | possibleSymbols := []string{"e", "x", "0", "1"} 472 | 473 | t.Run("FindEndOfTape", func(t *testing.T) { 474 | mConfigurations := append(mConfigurations, findEndOfTapeTest) 475 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 476 | MConfigurations: mConfigurations, 477 | Tape: []string{"e", "e", "0", " ", "1", " ", "0", " ", "1"}, 478 | PossibleSymbols: possibleSymbols, 479 | StartingMConfiguration: "b", 480 | })) 481 | m.MoveN(20) 482 | checkTape(t, m.TapeString(), "ee0 1 0 1 x") 483 | }) 484 | 485 | t.Run("FindRightMost", func(t *testing.T) { 486 | mConfigurations := append(mConfigurations, findRightMostTest) 487 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 488 | MConfigurations: mConfigurations, 489 | Tape: []string{"e", "e", "0", " ", "1", " ", "0", " ", "1"}, 490 | PossibleSymbols: possibleSymbols, 491 | StartingMConfiguration: "b", 492 | })) 493 | m.MoveN(20) 494 | checkTape(t, m.TapeString(), "ee0 1 0x1") 495 | }) 496 | } 497 | 498 | func TestPrintAtTheEnd2(t *testing.T) { 499 | // Invokes the printAtTheEnd2 MFunction (`pe2`), printing `x` and `y` at the end of the sequence 500 | printAtTheEndTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "pe2(halt, x, y)"} 501 | 502 | mConfigurations := []MConfiguration{} 503 | mConfigurations = append(mConfigurations, findLeftMost...) 504 | mConfigurations = append(mConfigurations, printAtTheEnd...) 505 | mConfigurations = append(mConfigurations, printAtTheEnd2...) 506 | mConfigurations = append(mConfigurations, printAtTheEndTest) 507 | possibleSymbols := []string{"e", "0", "x", "y"} 508 | 509 | t.Run("PrintAtTheEnd2", func(t *testing.T) { 510 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 511 | MConfigurations: mConfigurations, 512 | Tape: []string{"e", "e", "0", " ", "0"}, 513 | PossibleSymbols: possibleSymbols, 514 | StartingMConfiguration: "b", 515 | })) 516 | m.MoveN(30) 517 | checkTape(t, m.TapeString(), "ee0 0 x y") 518 | }) 519 | } 520 | 521 | func TestCopyAndErase2(t *testing.T) { 522 | // Invokes the copyAndErase5 MFunction (`ce5`), copying all of the marked figures and erasing all of the markers. 523 | copyAndEraseAll2Test := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "ce5(halt, x, s, t, u, v)"} 524 | 525 | mConfigurations := []MConfiguration{} 526 | mConfigurations = append(mConfigurations, findLeftMost...) 527 | mConfigurations = append(mConfigurations, findLeft...) 528 | mConfigurations = append(mConfigurations, printAtTheEnd...) 529 | mConfigurations = append(mConfigurations, erase...) 530 | mConfigurations = append(mConfigurations, copy...) 531 | mConfigurations = append(mConfigurations, copyAndErase...) 532 | mConfigurations = append(mConfigurations, copyAndErase2...) 533 | possibleSymbols := []string{"e", "0", "1", "x", "s", "t", "u", "v"} 534 | 535 | t.Run("CopyAndEraseAll2", func(t *testing.T) { 536 | mConfigurations := append(mConfigurations, copyAndEraseAll2Test) 537 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 538 | MConfigurations: mConfigurations, 539 | Tape: []string{"e", "e", "0", "x", "0", "x", "0", "s", "1", "s", "1", "t", "0", "t", "1", "u", "1", "u", "0", "v", "0", "v", "0"}, 540 | PossibleSymbols: possibleSymbols, 541 | StartingMConfiguration: "b", 542 | })) 543 | m.MoveN(1500) 544 | checkTape(t, m.TapeString(), "ee0 0 0 1 1 0 1 1 0 0") 545 | }) 546 | } 547 | 548 | func TestEraseAll(t *testing.T) { 549 | // Invokes the eraseAll MFunction (`e`), erasing all markers. 550 | eraseAllTest := MConfiguration{"b", []string{"*", " "}, []string{"R", "R"}, "e(halt)"} 551 | 552 | mConfigurations := []MConfiguration{} 553 | mConfigurations = append(mConfigurations, eraseAll...) 554 | possibleSymbols := []string{"e", "0", "x", "y"} 555 | 556 | t.Run("EraseAll", func(t *testing.T) { 557 | mConfigurations := append(mConfigurations, eraseAllTest) 558 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 559 | MConfigurations: mConfigurations, 560 | Tape: []string{"e", "e", "0", "x", "0", " ", "0", "y"}, 561 | PossibleSymbols: possibleSymbols, 562 | StartingMConfiguration: "b", 563 | })) 564 | m.MoveN(100) 565 | checkTape(t, m.TapeString(), "ee0 0 0") 566 | }) 567 | } 568 | -------------------------------------------------------------------------------- /beaver.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | haltMConfigurationName = "halt" 11 | maxMoves = 1000 12 | ) 13 | 14 | // Finds the m-configuration and number of `1`'s of the `n`'th busy beaver. 15 | func busyBeaver(n int, debug bool) (int, MachineInput) { 16 | // Initialize sets of m-configurations 17 | var mConfigurations []MConfiguration 18 | for i := 0; i < n; i++ { 19 | mConfigurations = append(mConfigurations, MConfiguration{ 20 | Name: strconv.Itoa(i), 21 | Symbols: []string{"0"}, 22 | Operations: []string{"P0", "L"}, // Print, then Move 23 | FinalMConfiguration: "0", 24 | }) 25 | mConfigurations = append(mConfigurations, MConfiguration{ 26 | Name: strconv.Itoa(i), 27 | Symbols: []string{"1"}, 28 | Operations: []string{"P0", "L"}, // Print, then Move 29 | FinalMConfiguration: "0", 30 | }) 31 | } 32 | 33 | // Keep track of the best so far 34 | var best int 35 | var bestMConfigurations []MConfiguration 36 | 37 | // The main bit 38 | for { 39 | // Run the current set of m-configurations 40 | if atLeastOneHaltState(mConfigurations) { 41 | result := simulateBusyBeaver(mConfigurations) 42 | if debug { 43 | mConfigurationsString := getMConfigurationsString(mConfigurations) 44 | fmt.Printf("best %d | result %d | %s\n", best, result, mConfigurationsString) 45 | } 46 | if result > best { 47 | best = result 48 | bestMConfigurations = mConfigurations 49 | } 50 | } 51 | 52 | var over bool 53 | for i := 0; i < n*2; i++ { 54 | // Iterate to the next the m-configuration 55 | nextMConfiguration, reset := nextMConfiguration(n, mConfigurations[i]) 56 | mConfigurations[i] = nextMConfiguration 57 | 58 | // If we don't need to reset the next m-configuration, move on 59 | if !reset { 60 | break 61 | } 62 | 63 | // We just reset all of our m-configurations (we've looped through everything) 64 | if i == (n*2)-1 { 65 | over = true 66 | } 67 | } 68 | 69 | // Check to see if we are done 70 | if over { 71 | break 72 | } 73 | } 74 | 75 | // Return the best we have 76 | return best, getBusyBeaverMachineInput(bestMConfigurations) 77 | } 78 | 79 | // Iterate through all of the variables of an m-configuration, return true if we did a full loop 80 | func nextMConfiguration(n int, mConfiguration MConfiguration) (MConfiguration, bool) { 81 | // Print Operations: P0, P1 82 | if mConfiguration.Operations[0] == "P0" { 83 | return MConfiguration{ 84 | Name: mConfiguration.Name, 85 | Symbols: mConfiguration.Symbols, 86 | Operations: []string{"P1", mConfiguration.Operations[1]}, 87 | FinalMConfiguration: mConfiguration.FinalMConfiguration, 88 | }, false 89 | } 90 | 91 | // Move Operations: L, R 92 | if mConfiguration.Operations[1] == "L" { 93 | return MConfiguration{ 94 | Name: mConfiguration.Name, 95 | Symbols: mConfiguration.Symbols, 96 | Operations: []string{"P0", "R"}, 97 | FinalMConfiguration: mConfiguration.FinalMConfiguration, 98 | }, false 99 | } 100 | 101 | // Final m-configurations: 0...n, halt 102 | if mConfiguration.FinalMConfiguration != haltMConfigurationName { 103 | finalMConfigurationInt, _ := strconv.Atoi(mConfiguration.FinalMConfiguration) 104 | var finalMConfiguration string 105 | if finalMConfigurationInt == n-1 { 106 | finalMConfiguration = haltMConfigurationName 107 | } else { 108 | finalMConfiguration = strconv.Itoa(finalMConfigurationInt + 1) 109 | } 110 | return MConfiguration{ 111 | Name: mConfiguration.Name, 112 | Symbols: mConfiguration.Symbols, 113 | Operations: []string{"P0", "L"}, 114 | FinalMConfiguration: finalMConfiguration, 115 | }, false 116 | } 117 | 118 | // If we reset the full m-configuration, signal upwards that the next m-configuration in the list should iterate 119 | return MConfiguration{ 120 | Name: mConfiguration.Name, 121 | Symbols: mConfiguration.Symbols, 122 | Operations: []string{"P0", "L"}, 123 | FinalMConfiguration: "0", 124 | }, true 125 | } 126 | 127 | // No need to simulate if we know the MConfiguration will never halt 128 | func atLeastOneHaltState(mConfigurations []MConfiguration) bool { 129 | for _, mConfiguration := range mConfigurations { 130 | if mConfiguration.FinalMConfiguration == haltMConfigurationName { 131 | return true 132 | } 133 | } 134 | return false 135 | } 136 | 137 | // Return the amount of `1`'s the machine prints up to `maxMoves` 138 | func simulateBusyBeaver(mConfigurations []MConfiguration) int { 139 | m := NewMachine(getBusyBeaverMachineInput(mConfigurations)) 140 | moves := m.MoveN(maxMoves) 141 | if moves == maxMoves { 142 | return 0 143 | } 144 | 145 | var count int 146 | for _, square := range m.Tape() { 147 | if square == "1" { 148 | count++ 149 | } 150 | } 151 | return count 152 | } 153 | 154 | // For a set of our m-configurations, give a runnable MachineInput 155 | func getBusyBeaverMachineInput(mConfigurations []MConfiguration) MachineInput { 156 | return MachineInput{ 157 | MConfigurations: mConfigurations, 158 | PossibleSymbols: []string{"1"}, 159 | NoneSymbol: "0", 160 | } 161 | } 162 | 163 | // Shortens m-configurations for printing/debugging 164 | func getMConfigurationsString(mConfigurations []MConfiguration) string { 165 | var s strings.Builder 166 | 167 | for i, mConfiguration := range mConfigurations { 168 | if i%2 == 0 { 169 | s.WriteString(fmt.Sprintf("%s[", mConfiguration.Name)) 170 | } 171 | 172 | s.WriteString(fmt.Sprintf(" %s:%s;%s;%s", mConfiguration.Symbols[0], mConfiguration.Operations[0], mConfiguration.Operations[1], mConfiguration.FinalMConfiguration)) 173 | 174 | if i%2 == 0 { 175 | s.WriteString(fmt.Sprintf(",")) 176 | } else { 177 | s.WriteString(fmt.Sprintf(" ] ")) 178 | } 179 | } 180 | 181 | return s.String() 182 | } 183 | -------------------------------------------------------------------------------- /beaver_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import "testing" 4 | 5 | func TestFirstBusyBeaver(t *testing.T) { 6 | testBusyBeaver(t, 1, 1, false) 7 | } 8 | 9 | func TestSecondBusyBeaver(t *testing.T) { 10 | testBusyBeaver(t, 2, 4, false) 11 | } 12 | 13 | // BB-3 and beyond take quite a bit of time... 14 | 15 | // func TestThirdBusyBeaver(t *testing.T) { 16 | // testBusyBeaver(t, 3, 6, false) 17 | // } 18 | 19 | // func TestFourthBusyBeaver(t *testing.T) { 20 | // testBusyBeaver(t, 4, 13, false) 21 | // } 22 | 23 | func testBusyBeaver(t *testing.T, n int, expected int, debug bool) { 24 | actual, _ := busyBeaver(n, debug) 25 | if actual != expected { 26 | t.Errorf("Incorrect BB-%d number %d, expected %d", n, actual, expected) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /decision.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Implement `NewDecisionMachine` 4 | -------------------------------------------------------------------------------- /decision_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Test `NewDecisionMachine` 4 | -------------------------------------------------------------------------------- /diagonal.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // The following m-functions and m-configurations test if a Standard Description 4 | // on the Tape is well-defined. It is assumed that the head of the Tape is at the 5 | // start of the S.D. 6 | var ( 7 | wellDefinedMachinePossibleSymbols = []string{ 8 | string(a), 9 | string(c), 10 | string(d), 11 | string(l), 12 | string(r), 13 | string(n), 14 | string(semicolon), 15 | "s", 16 | "u", 17 | } 18 | 19 | wellDefinedMachineMConfigurations = []MConfiguration{ 20 | // The start of the machine 21 | {"b", []string{"*", " "}, []string{}, "checkSemicolon"}, 22 | 23 | // The decision of well-definedness is satisfactory 24 | {"satisfactory", []string{"*", " "}, []string{}, "decide(s)"}, 25 | 26 | // The decision of well-definedness is unsatisfactory 27 | {"unsatisfactory", []string{"*", " "}, []string{}, "decide(u)"}, 28 | 29 | // Erase everything and print the decision 30 | {"decide(d)", []string{"*"}, []string{"R", "R"}, "decide(d)"}, 31 | {"decide(d)", []string{" "}, []string{"L", "L"}, "decide1(d)"}, 32 | {"decide1(d)", []string{"*"}, []string{"E", "L", "L"}, "decide(d)"}, 33 | {"decide1(d)", []string{" "}, []string{"Pd"}, "halt"}, 34 | 35 | // Check the semicolon that deliminates the S.D. 36 | {"checkSemicolon", []string{";"}, []string{"R", "R"}, "checkName"}, 37 | {"checkSemicolon", []string{"!;"}, []string{}, "unsatisfactory"}, 38 | {"checkSemicolon", []string{" "}, []string{"L", "L"}, "checkSemicolon1"}, 39 | {"checkSemicolon1", []string{" "}, []string{}, "unsatisfactory"}, 40 | {"checkSemicolon1", []string{"*"}, []string{"R", "R"}, "satisfactory"}, 41 | 42 | // Check the name portion of the S.D. subsegment 43 | {"checkName", []string{"D"}, []string{"R", "R"}, "checkName1"}, 44 | {"checkName", []string{"!D", " "}, []string{}, "unsatisfactory"}, 45 | {"checkName1", []string{"A"}, []string{"R", "R"}, "checkName1"}, 46 | {"checkName1", []string{"!A", " "}, []string{}, "checkSymbol"}, 47 | 48 | // Check the symbol portion of the S.D. subsegment 49 | {"checkSymbol", []string{"D"}, []string{"R", "R"}, "checkSymbol1"}, 50 | {"checkSymbol", []string{"!D", " "}, []string{}, "unsatisfactory"}, 51 | {"checkSymbol1", []string{"C"}, []string{"R", "R"}, "checkSymbol1"}, 52 | {"checkSymbol1", []string{"!C", " "}, []string{}, "checkPrintOp"}, 53 | 54 | // Check the print operation portion of the S.D. subsegment 55 | {"checkPrintOp", []string{"D"}, []string{"R", "R"}, "checkPrintOp1"}, 56 | {"checkPrintOp", []string{"!D", " "}, []string{}, "unsatisfactory"}, 57 | {"checkPrintOp1", []string{"C"}, []string{"R", "R"}, "checkPrintOp1"}, 58 | {"checkPrintOp1", []string{"!C", " "}, []string{}, "checkMoveOp"}, 59 | 60 | // Check the move operation portion of the S.D. subsegment 61 | {"checkMoveOp", []string{"L", "R", "N"}, []string{"R", "R"}, "checkFinalMConfig"}, 62 | {"checkMoveOp", []string{"!L", "!R", "!N", " "}, []string{}, "unsatisfactory"}, 63 | 64 | // Check the final m-config portion of the S.D. subsegment 65 | {"checkFinalMConfig", []string{"D"}, []string{"R", "R"}, "checkFinalMConfig1"}, 66 | {"checkFinalMConfig", []string{"!D", " "}, []string{}, "unsatisfactory"}, 67 | {"checkFinalMConfig1", []string{"A"}, []string{"R", "R"}, "checkFinalMConfig1"}, 68 | {"checkFinalMConfig1", []string{"!A", " "}, []string{}, "checkSemicolon"}, 69 | } 70 | ) 71 | 72 | // The following defines Turing's `H` machine. The entire machine is implemented 73 | // with the exception of the `D` machine (which is not possible). 74 | var ( 75 | hMachinePossibleSymbols = []string{} 76 | 77 | hMachineMConfigurations = []MConfiguration{ 78 | // TODO: The start of the machine 79 | {"beginH", []string{"*", " "}, []string{"Pe", "R", "Pe", "R", "P:::"}, "TODO"}, 80 | 81 | // TODO: Find the 2nd to last `:` and copy what comes after to the end of the tape, 82 | // but increment this number by 1. Afterwards, print `::` and move to `convert`. 83 | {"iter", []string{"*", " "}, []string{}, "TODO"}, 84 | 85 | // TODO: Find the latest `::` and convert the D.N. to the left into a S.D. on the right. 86 | // Afterwards, print `:::` invoke `D`. 87 | {"convert", []string{"*", " "}, []string{}, "TODO"}, 88 | 89 | // TODO: Fake `D` 90 | {"D", []string{"*", " "}, []string{}, "TODO"}, 91 | 92 | // TODO: Check for `s` or `u`. If `u`, print `:`, and move back to `iter`. 93 | // If `s`, print `::::`, and move to `R`. 94 | {"check", []string{"*", " "}, []string{}, "TODO"}, 95 | 96 | // TODO: Find the most recent `R` after the 2nd to last `::::` and copy it to the end 97 | // of the tape. Add one more symbol to increment. Print `:::::` and move to `simulate`. 98 | {"R", []string{"*", " "}, []string{}, "TODO"}, 99 | 100 | // TODO: Copy the S.D. after the most recent `::` to the end of the tape. 101 | // Use `U` to simulate the machine, with the modification of only printing 102 | // `R` characters. After this has happened, print `::::::` move to `print`. 103 | {"simulate", []string{"*", " "}, []string{}, "TODO"}, 104 | 105 | // TODO: Pluck the `R`'th character from the complete configuration after `:::::` 106 | // and print it after `::::::`. Now print `:` and move back to `iter`. 107 | {"print", []string{"*", " "}, []string{}, "TODO"}, 108 | } 109 | ) 110 | 111 | // The following defines Turing's `G` machine. The entire machine is implemented 112 | // with the exception of the `E` machine (which is not possible). 113 | var ( 114 | gMachinePossibleSymbols = []string{} 115 | 116 | gMachineMConfigurations = []MConfiguration{ 117 | // TODO: The start of the machine 118 | {"b", []string{"*", " "}, []string{"Pe", "R", "Pe", "R", "P:"}, "TODO"}, 119 | 120 | // TODO: Enumerate m-functions 121 | } 122 | ) 123 | -------------------------------------------------------------------------------- /diagonal_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestFirstCircularDN(t *testing.T) { 9 | _, err := NewMachineFromDescriptionNumber(DescriptionNumber("1")) 10 | if err == nil { 11 | t.Error("expecting D.N. 1 to be circular") 12 | } 13 | } 14 | 15 | func TestFirstCircleFreeDN(t *testing.T) { 16 | machineInput, err := NewMachineFromDescriptionNumber(DescriptionNumber("731332531")) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | m := NewMachine(machineInput) 22 | m.MoveN(100) 23 | checkTape(t, m.TapeString(), "S1S1S1S1S1S1S1") 24 | } 25 | 26 | func TestWellDefinedness(t *testing.T) { 27 | m := NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 28 | MConfigurations: wellDefinedMachineMConfigurations, 29 | Tape: strings.Split("; D A D A D A D", ""), 30 | StartingMConfiguration: "b", 31 | PossibleSymbols: wellDefinedMachinePossibleSymbols, 32 | })) 33 | 34 | m.MoveN(100000) 35 | if m.TapeString()[0] != 'u' { 36 | t.Errorf("expecting unsatisfactory but got %s", m.TapeString()) 37 | } 38 | 39 | m = NewMachine(NewAbbreviatedTable(AbbreviatedTableInput{ 40 | MConfigurations: wellDefinedMachineMConfigurations, 41 | Tape: strings.Split("; D A D D C R D A", ""), 42 | StartingMConfiguration: "b", 43 | PossibleSymbols: wellDefinedMachinePossibleSymbols, 44 | })) 45 | 46 | m.MoveN(100000) 47 | if m.TapeString()[0] != 's' { 48 | t.Errorf("expecting satisfactory but got %s", m.TapeString()) 49 | } 50 | } 51 | 52 | // TODO: Test non-`D` part of `H` 53 | 54 | // TODO: Test `M1`, `M2`, etc. 55 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/planetlambert/turing 2 | 3 | go 1.21.0 4 | -------------------------------------------------------------------------------- /hilbert.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Implement `K` and `K_a` 4 | -------------------------------------------------------------------------------- /hilbert_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Test `K` and `K_a` 4 | -------------------------------------------------------------------------------- /lambda.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Implement `NewLambdaMachine` 4 | -------------------------------------------------------------------------------- /lambda_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | // TODO: Implement `NewLambdaMachine` 4 | -------------------------------------------------------------------------------- /machine.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "strings" 7 | ) 8 | 9 | type ( 10 | // We may compare a man in the process of computing a real number to a machine... 11 | MachineInput struct { 12 | // ...which is only capable of a finite number of conditions q1, q2, ..., qR which 13 | // will be called "m-configurations". 14 | MConfigurations []MConfiguration 15 | 16 | // The machine is supplied with a "tape" (the analogue of paper) running through it, 17 | // and divided into sections (called "squares") each capable of bearing a "symbol". 18 | Tape Tape 19 | 20 | // The m-configuration that the machine should start with. If empty the first m-configuration 21 | // in the list is chosen. 22 | StartingMConfiguration string 23 | 24 | // A list of all symbols the machine is capable of reading or printing. 25 | // This field is used when dealing with special symbols `*` (Any), `!` (Not) 26 | // Note: The ` ` (None) symbol does not have to be provided (it is assumed). 27 | PossibleSymbols []string 28 | 29 | // Defaults to ` ` (None), but can optionally be overridden here. 30 | NoneSymbol string 31 | 32 | // If `true`, the machine's complete configurations are printed at the end of each move. 33 | Debug bool 34 | } 35 | 36 | // Turing's Machine 37 | Machine struct { 38 | // See corresponding input field 39 | mConfigurations []MConfiguration 40 | 41 | // See corresponding input field 42 | tape []string 43 | 44 | // See corresponding input field 45 | possibleSymbols []string 46 | 47 | // See corresponding input field 48 | noneSymbol string 49 | 50 | // See corresponding input field 51 | debug bool 52 | 53 | // At any moment there is just one square, say the r-th, bearing the symbol S(r) 54 | // which is "in the machine". We may call this square the "scanned square". 55 | // The symbol on the scanned square may be called the "scanned symbol". 56 | // The "scanned symbol" is the only one of which the machine is, so to speak, "directly aware". 57 | scannedSquare int 58 | 59 | // The current m-configuration of the machine. 60 | currentMConfigurationName string 61 | 62 | // Stores whether the machine has "halted" or not. A machine only halts if it cannot 63 | // find an m-configuration. 64 | halted bool 65 | } 66 | 67 | // An m-configuration contains four components 68 | MConfiguration struct { 69 | // The possible behaviour of the machine at any moment is determined by the m-configuration qn... 70 | Name string 71 | 72 | // ...and the scanned symbol S(r) 73 | Symbols []string 74 | 75 | // In some of the configurations in which the scanned square is blank (i.e. bears no symbol) 76 | // the machine writes down a new symbol on the scanned square: in other configurations it 77 | // erases the scanned symbol. The machine may also change the square which is being scanned, 78 | // but only by shifting it one place to right or left. 79 | Operations []string 80 | 81 | // In addition to any of these operations the m-configuration may be changed. 82 | FinalMConfiguration string 83 | } 84 | 85 | // Our "tape" is a slice of strings because squares can contain multiple characters 86 | Tape []string 87 | 88 | // Well-known single-character codes used in an m-configuration's operations. 89 | operationCode byte 90 | ) 91 | 92 | const ( 93 | rightOp operationCode = 'R' 94 | leftOp operationCode = 'L' 95 | eraseOp operationCode = 'E' 96 | printOp operationCode = 'P' 97 | 98 | none string = " " 99 | not string = "!" 100 | any string = "*" 101 | ) 102 | 103 | // Returns a new Machine 104 | func NewMachine(input MachineInput) *Machine { 105 | m := &Machine{ 106 | mConfigurations: input.MConfigurations, 107 | debug: input.Debug, 108 | } 109 | 110 | // Use first m-configuration if starting m-configuration not specified 111 | if len(input.StartingMConfiguration) == 0 { 112 | m.currentMConfigurationName = input.MConfigurations[0].Name 113 | } else { 114 | m.currentMConfigurationName = input.StartingMConfiguration 115 | } 116 | 117 | // Use default None character if not specified 118 | if len(input.NoneSymbol) == 0 { 119 | m.noneSymbol = none 120 | } else { 121 | m.noneSymbol = input.NoneSymbol 122 | } 123 | 124 | // Initialize tape if nil 125 | if input.Tape == nil { 126 | m.tape = []string{} 127 | } else { 128 | m.tape = input.Tape 129 | } 130 | 131 | if m.debug { 132 | m.printMConfigurationsForDebug() 133 | } 134 | 135 | return m 136 | } 137 | 138 | // Moves the machine n times and stops early if halted. Returns the amount of moves the machine took. 139 | func (m *Machine) MoveN(n int) int { 140 | for i := 1; i <= n; i++ { 141 | m.Move() 142 | if m.halted { 143 | return i 144 | } 145 | } 146 | return n 147 | } 148 | 149 | // Moves the machine once 150 | func (m *Machine) Move() { 151 | if m.halted { 152 | return 153 | } 154 | 155 | // Scan symbol from the tape 156 | symbol := m.scan() 157 | 158 | // Find the the correct m-configuration depending on the scanned synbol 159 | mConfiguration, shouldHalt := m.findMConfiguration(m.currentMConfigurationName, symbol) 160 | 161 | // If an m-configuration could not be found, halt the machine 162 | if shouldHalt { 163 | m.halted = true 164 | return 165 | } 166 | 167 | // Perform operations 168 | for _, operation := range mConfiguration.Operations { 169 | m.performOperation(operation) 170 | } 171 | 172 | if m.debug { 173 | m.printCompleteConfigurationForDebug() 174 | } 175 | 176 | // Move to specified final-m-configuration 177 | m.currentMConfigurationName = mConfiguration.FinalMConfiguration 178 | } 179 | 180 | // Returns the Machine's Tape 181 | func (m *Machine) Tape() Tape { 182 | return m.tape 183 | } 184 | 185 | // Return the Tape represented as a string 186 | func (m *Machine) TapeString() string { 187 | return strings.Join([]string(m.tape), "") 188 | } 189 | 190 | // Returns the machine's Complete Configuration of the single-line form 191 | func (m *Machine) CompleteConfiguration() string { 192 | var completeConfiguration strings.Builder 193 | for i, square := range m.tape { 194 | if i == m.scannedSquare { 195 | completeConfiguration.WriteString(m.currentMConfigurationName) 196 | } 197 | completeConfiguration.WriteString(square) 198 | } 199 | if m.scannedSquare == len(m.tape) { 200 | completeConfiguration.WriteString(m.currentMConfigurationName) 201 | } 202 | return completeConfiguration.String() 203 | } 204 | 205 | // Scans the tape for the scanned symbol 206 | func (m *Machine) scan() string { 207 | m.extendTapeIfNeeded() 208 | return m.tape[m.scannedSquare] 209 | } 210 | 211 | // The Machine's Tape is infinite, so we extend it as-needed 212 | func (m *Machine) extendTapeIfNeeded() { 213 | if m.scannedSquare >= len(m.tape) { 214 | m.tape = append(m.tape, m.noneSymbol) 215 | } 216 | if m.scannedSquare < 0 { 217 | m.tape = append([]string{m.noneSymbol}, m.tape...) 218 | m.scannedSquare++ 219 | } 220 | } 221 | 222 | // Find the appropriate full m-configuration given the current m-configuration name and the scanned symbol 223 | func (m *Machine) findMConfiguration(mConfigurationName string, symbol string) (MConfiguration, bool) { 224 | for _, mConfiguration := range m.mConfigurations { 225 | if mConfiguration.Name == mConfigurationName { 226 | // Scenario 1: The provided symbol is contained exactly in the m-configuration 227 | if slices.Contains(mConfiguration.Symbols, symbol) { 228 | return mConfiguration, false 229 | } 230 | 231 | if symbol != m.noneSymbol { 232 | // Scenario 2: The m-configuration contains `*` 233 | // Note that `*` does not include ` ` (None), which must be specified manually 234 | if slices.Contains(mConfiguration.Symbols, any) { 235 | return mConfiguration, false 236 | } 237 | 238 | // Scenario 3: The MConfiguration contains `!x` where `x` is not the provided symbol 239 | // Note that `!` does not include ` ` (None), which must be specified manually 240 | notSymbols := []string{} 241 | // First loop is required in the scenario we have multiple (`!x` and `!y`) 242 | for _, mConfigurationSymbol := range mConfiguration.Symbols { 243 | if strings.Contains(mConfigurationSymbol, not) { 244 | notSymbols = append(notSymbols, mConfigurationSymbol[1:]) 245 | } 246 | } 247 | if len(notSymbols) > 0 && !slices.Contains(notSymbols, symbol) { 248 | return mConfiguration, false 249 | } 250 | } 251 | } 252 | } 253 | return MConfiguration{}, true 254 | } 255 | 256 | // Perform an operation 257 | func (m *Machine) performOperation(operation string) { 258 | m.extendTapeIfNeeded() 259 | switch operationCode(operation[0]) { 260 | case rightOp: 261 | m.scannedSquare++ 262 | case leftOp: 263 | m.scannedSquare-- 264 | case eraseOp: 265 | m.tape[m.scannedSquare] = m.noneSymbol 266 | case printOp: 267 | m.tape[m.scannedSquare] = string(operation[1:]) 268 | } 269 | } 270 | 271 | // Prints the m-configurations of the machine nicely for debugging 272 | func (m *Machine) printMConfigurationsForDebug() { 273 | for _, mConfiguration := range m.mConfigurations { 274 | fmt.Printf("%s %v %v %s\n", mConfiguration.Name, mConfiguration.Symbols, mConfiguration.Operations, mConfiguration.FinalMConfiguration) 275 | } 276 | } 277 | 278 | // Prints the complete configuration for the machine nicely for debugging 279 | func (m *Machine) printCompleteConfigurationForDebug() { 280 | for _, square := range m.tape { 281 | fmt.Print(strings.Repeat("-", len(square))) 282 | } 283 | fmt.Println("-") 284 | fmt.Println(m.TapeString()) 285 | for i, square := range m.tape { 286 | if i >= m.scannedSquare { 287 | break 288 | } 289 | fmt.Print(strings.Repeat(" ", len(square))) 290 | } 291 | fmt.Println(m.currentMConfigurationName) 292 | } 293 | -------------------------------------------------------------------------------- /machine_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestMachineExample1(t *testing.T) { 9 | m := NewMachine(MachineInput{ 10 | MConfigurations: []MConfiguration{ 11 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 12 | {"c", []string{" "}, []string{"R"}, "e"}, 13 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 14 | {"k", []string{" "}, []string{"R"}, "b"}, 15 | }, 16 | }) 17 | m.MoveN(50) 18 | checkTape(t, m.TapeString(), "0 1 0 1 0 1 0 1 0 1 0 1") 19 | } 20 | 21 | func TestMachineExample1Short(t *testing.T) { 22 | m := NewMachine(MachineInput{ 23 | MConfigurations: []MConfiguration{ 24 | {"b", []string{" "}, []string{"P0"}, "b"}, 25 | {"b", []string{"0"}, []string{"R", "R", "P1"}, "b"}, 26 | {"b", []string{"1"}, []string{"R", "R", "P0"}, "b"}, 27 | }, 28 | }) 29 | m.MoveN(50) 30 | checkTape(t, m.TapeString(), "0 1 0 1 0 1 0 1 0 1 0 1") 31 | } 32 | 33 | func TestMachineExample2(t *testing.T) { 34 | m := NewMachine(MachineInput{ 35 | MConfigurations: []MConfiguration{ 36 | {"b", []string{"*", " "}, []string{"Pe", "R", "Pe", "R", "P0", "R", "R", "P0", "L", "L"}, "o"}, 37 | {"o", []string{"1"}, []string{"R", "Px", "L", "L", "L"}, "o"}, 38 | {"o", []string{"0"}, []string{}, "q"}, 39 | {"q", []string{"0", "1"}, []string{"R", "R"}, "q"}, 40 | {"q", []string{" "}, []string{"P1", "L"}, "p"}, 41 | {"p", []string{"x"}, []string{"E", "R"}, "q"}, 42 | {"p", []string{"e"}, []string{"R"}, "f"}, 43 | {"p", []string{" "}, []string{"L", "L"}, "p"}, 44 | {"f", []string{"*"}, []string{"R", "R"}, "f"}, 45 | {"f", []string{" "}, []string{"P0", "L", "L"}, "o"}, 46 | }, 47 | }) 48 | 49 | m.Move() 50 | checkCompleteConfiguration(t, m.CompleteConfiguration(), "eeo0 0") 51 | m.Move() 52 | checkCompleteConfiguration(t, m.CompleteConfiguration(), "eeq0 0") 53 | m.Move() 54 | checkCompleteConfiguration(t, m.CompleteConfiguration(), "ee0 q0") 55 | m.Move() 56 | checkCompleteConfiguration(t, m.CompleteConfiguration(), "ee0 0 q") 57 | m.Move() 58 | checkCompleteConfiguration(t, m.CompleteConfiguration(), "ee0 0p 1") 59 | // ... 60 | 61 | m.MoveN(200) 62 | checkTape(t, m.TapeString(), "ee0 0 1 0 1 1 0 1 1 1 0 1 1 1 1") 63 | } 64 | 65 | func checkTape(t *testing.T, tape string, expectedStart string) { 66 | if !strings.HasPrefix(tape, expectedStart) { 67 | var actual string 68 | if len(expectedStart)+10 <= len(tape) { 69 | actual = tape[0 : len(expectedStart)+10] 70 | } else { 71 | actual = tape 72 | } 73 | t.Errorf("got %s, want %s", actual, expectedStart) 74 | } 75 | } 76 | 77 | func checkCompleteConfiguration(t *testing.T, actual string, expected string) { 78 | if actual != expected { 79 | t.Errorf("got %s, want %s", actual, expected) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /standard.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "regexp" 7 | "slices" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type ( 13 | // Our StandardTable is a wrapper for Turing's standard forms 14 | StandardTable struct { 15 | // The first is input to a machine that has been standardized. 16 | MachineInput MachineInput 17 | // The second is a mapping from our new symbols to our symbols. 18 | // This is not essential but helps with debugging and testing. 19 | SymbolMap SymbolMap 20 | // Turing's Standard Description (S.D.) 21 | StandardDescription StandardDescription 22 | // Turing's Description Number (D.N.) 23 | DescriptionNumber DescriptionNumber 24 | } 25 | 26 | // Struct to hold shared values when standardizing MachineInput 27 | standardTableCreator struct { 28 | input MachineInput 29 | mConfigurationNames map[string]string 30 | nameCount int 31 | mConfigurationSymbols map[string]string 32 | symbolCount int 33 | } 34 | 35 | // A map of new symbols to old symbols, used to verify Tape output 36 | SymbolMap map[string]string 37 | 38 | // A string representing the full m-configuration list of a Machine 39 | StandardDescription string 40 | 41 | // The StandardDescription converted uniquely to a number 42 | DescriptionNumber string 43 | ) 44 | 45 | const ( 46 | mConfigurationNamePrefix string = "q" 47 | mConfigurationSymbolPrefix string = "S" 48 | 49 | a byte = 'A' 50 | c byte = 'C' 51 | d byte = 'D' 52 | l byte = 'L' 53 | r byte = 'R' 54 | n byte = 'N' 55 | semicolon byte = ';' 56 | ) 57 | 58 | var ( 59 | sdCharToDNInt = map[byte]int{ 60 | a: 1, 61 | c: 2, 62 | d: 3, 63 | l: 4, 64 | r: 5, 65 | n: 6, 66 | semicolon: 7, 67 | } 68 | 69 | dnIntToSDChar = map[int]byte{ 70 | 1: a, 71 | 2: c, 72 | 3: d, 73 | 4: l, 74 | 5: r, 75 | 6: n, 76 | 7: semicolon, 77 | } 78 | ) 79 | 80 | // Standardizes MachineInput so it conforms to Turing's standard form. 81 | func NewStandardTable(input MachineInput) StandardTable { 82 | s := &standardTableCreator{ 83 | input: input, 84 | } 85 | 86 | return s.standardize() 87 | } 88 | 89 | // Converts a Machine to a Machine that conforms to Turing's standard form. 90 | func (s *standardTableCreator) standardize() StandardTable { 91 | // The new m-configurations of the machine 92 | standardMConfigurations := []MConfiguration{} 93 | 94 | // Turing prefers a format where ` ` (None) is S0, `0` is S1, `1` is S2 and so on 95 | // This ensures ` ` (None) comes first 96 | s.newMConfigurationSymbol(none) 97 | 98 | // Every m-configuration will be rewritten and potentially introduce further m-configurations 99 | for _, mConfiguration := range s.input.MConfigurations { 100 | // Enumerate all symbols for the m-configuration in standard form 101 | symbols := s.expandStandardSymbols(mConfiguration.Symbols) 102 | 103 | // Split out the operations so they satisfy Turing's acceptable forms: 104 | // (E), (E, R), (E, L), (Pa), (Pa, R), (Pa, L), (R), (L), () 105 | printOperations, moveOperations := s.expandStandardOperations(mConfiguration.Operations) 106 | 107 | // Standardize m-configuration name 108 | name := s.newMConfigurationName(mConfiguration.Name) 109 | 110 | // Standardize final m-configuration 111 | finalMConfiguration := s.newMConfigurationName(mConfiguration.FinalMConfiguration) 112 | 113 | // For each symbol, make identical m-configurations 114 | for _, currentSymbol := range symbols { 115 | 116 | // For each operation for this symbol, calculate the m-configuration 117 | var nextName string 118 | for i := 0; i < len(printOperations); i++ { 119 | // Only the first operation in a list needs a name that others will recognize 120 | var calculatedName string 121 | if i == 0 { 122 | calculatedName = name 123 | } else { 124 | calculatedName = nextName 125 | } 126 | 127 | // Similarly, only the final operation in a list needs a name that others will recognize 128 | var calculatedFinalMConfiguration string 129 | if i == len(printOperations)-1 { 130 | calculatedFinalMConfiguration = finalMConfiguration 131 | } else { 132 | nextName = s.newHiddenMConfigurationName() 133 | calculatedFinalMConfiguration = nextName 134 | } 135 | 136 | // If we intend to print a 'Noop', just use the current symbol 137 | calculatedPrintOperation := s.calculateStandardPrintOperation(printOperations[i], currentSymbol) 138 | 139 | if i == 0 { 140 | // Only one m-configuration needed 141 | standardMConfigurations = append(standardMConfigurations, MConfiguration{ 142 | Name: calculatedName, 143 | Symbols: []string{currentSymbol}, 144 | Operations: []string{calculatedPrintOperation, moveOperations[i]}, 145 | FinalMConfiguration: calculatedFinalMConfiguration, 146 | }) 147 | } else { 148 | // When we are in hidden states, we get to the final m-configuration no matter what 149 | // This means we need to account for all symbols 150 | for _, calculatedSymbol := range append(s.input.PossibleSymbols, none) { 151 | // If we intend to print a 'Noop', just use the current symbol 152 | calculatedPrintOperation := s.calculateStandardPrintOperation(printOperations[i], s.newMConfigurationSymbol(calculatedSymbol)) 153 | 154 | standardMConfigurations = append(standardMConfigurations, MConfiguration{ 155 | Name: calculatedName, 156 | Symbols: []string{s.newMConfigurationSymbol(calculatedSymbol)}, 157 | Operations: []string{calculatedPrintOperation, moveOperations[i]}, 158 | FinalMConfiguration: calculatedFinalMConfiguration, 159 | }) 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | machineInput := MachineInput{ 167 | MConfigurations: standardMConfigurations, 168 | Tape: s.newTape(), 169 | StartingMConfiguration: s.newStartingMConfiguration(), 170 | PossibleSymbols: s.newMConfigurationSymbols(), 171 | NoneSymbol: s.newMConfigurationSymbol(none), 172 | } 173 | sd := toStandardDescription(machineInput) 174 | dn := toDescriptionNumber(sd) 175 | 176 | return StandardTable{ 177 | MachineInput: machineInput, 178 | SymbolMap: s.reverseMConfigurationSymbols(), 179 | StandardDescription: sd, 180 | DescriptionNumber: dn, 181 | } 182 | } 183 | 184 | // Expands and standardizes the list of symbols (to the form S0, S1, ..., etc.) 185 | func (s *standardTableCreator) expandStandardSymbols(originalSymbols []string) []string { 186 | // First loop required for multiple Not scenario 187 | notSymbols := []string{} 188 | for _, symbol := range originalSymbols { 189 | if strings.Contains(symbol, not) { 190 | notSymbols = append(notSymbols, symbol[1:]) 191 | } 192 | } 193 | 194 | symbols := []string{} 195 | for _, symbol := range originalSymbols { 196 | // To support `!` (Not), `*` (Any), etc. we may need multiple m-configurations for this one particular row 197 | if strings.Contains(symbol, not) { 198 | for _, possibleSymbol := range s.input.PossibleSymbols { 199 | if !slices.Contains(notSymbols, possibleSymbol) && !slices.Contains(symbols, possibleSymbol) { 200 | symbols = append(symbols, s.newMConfigurationSymbol(possibleSymbol)) 201 | } 202 | } 203 | } else if symbol == any { 204 | for _, possibleSymbol := range s.input.PossibleSymbols { 205 | symbols = append(symbols, s.newMConfigurationSymbol(possibleSymbol)) 206 | } 207 | } else { 208 | symbols = append(symbols, s.newMConfigurationSymbol(symbol)) 209 | } 210 | } 211 | return symbols 212 | } 213 | 214 | // Standardizes the list of options to the form Turing prefers (exactly one Print and one Move operation) 215 | // These are returned in two slices - the Print operation slice and the Move operation slice 216 | func (s *standardTableCreator) expandStandardOperations(originalOperations []string) ([]string, []string) { 217 | printOperations := []string{} 218 | moveOperations := []string{} 219 | if len(originalOperations) == 0 { 220 | printOperations = append(printOperations, string(printOp)) 221 | moveOperations = append(moveOperations, string(n)) 222 | } else { 223 | lookingForPrint := true 224 | for i, operation := range originalOperations { 225 | operationCode := operationCode(operation[0]) 226 | if lookingForPrint { 227 | if operationCode == printOp { 228 | symbol := string(operation[1:]) 229 | var printOperation strings.Builder 230 | printOperation.WriteByte(byte(printOp)) 231 | printOperation.WriteString(s.newMConfigurationSymbol(symbol)) 232 | printOperations = append(printOperations, printOperation.String()) 233 | lookingForPrint = false 234 | if i == len(originalOperations)-1 { 235 | moveOperations = append(moveOperations, string(n)) 236 | } 237 | } else if operationCode == eraseOp { 238 | var printOperation strings.Builder 239 | printOperation.WriteByte(byte(printOp)) 240 | printOperation.WriteString(s.newMConfigurationSymbol(none)) 241 | printOperations = append(printOperations, printOperation.String()) 242 | lookingForPrint = false 243 | if i == len(originalOperations)-1 { 244 | moveOperations = append(moveOperations, string(n)) 245 | } 246 | } else { 247 | var printOperation strings.Builder 248 | printOperation.WriteByte(byte(printOp)) 249 | // Printing the current symbol is essentially a Print noop 250 | // We encode this by just including `P` with no symbol 251 | printOperations = append(printOperations, printOperation.String()) 252 | moveOperations = append(moveOperations, string(operationCode)) 253 | } 254 | } else { 255 | if operationCode == leftOp || operationCode == rightOp { 256 | moveOperations = append(moveOperations, string(operationCode)) 257 | } else { 258 | moveOperations = append(moveOperations, string(n)) 259 | } 260 | lookingForPrint = true 261 | } 262 | } 263 | } 264 | return printOperations, moveOperations 265 | } 266 | 267 | // Returns the standardized print operation, taking into account the "Noop" print situation 268 | func (st *standardTableCreator) calculateStandardPrintOperation(printOperation string, currentSymbol string) string { 269 | var calculatedPrintOperation string 270 | // A Print operation with no value (just `P`) means we should perform a "Noop" print, 271 | // meaning just print whatever symbol is already on the scanned square 272 | if printOperation == string(printOp) { 273 | var calculatedPrintOperationBuilder strings.Builder 274 | calculatedPrintOperationBuilder.WriteByte(byte(printOp)) 275 | calculatedPrintOperationBuilder.WriteString(currentSymbol) 276 | calculatedPrintOperation = calculatedPrintOperationBuilder.String() 277 | } else { 278 | calculatedPrintOperation = printOperation 279 | } 280 | return calculatedPrintOperation 281 | } 282 | 283 | // Returns the standardized m-configuration name (of the form q1, q2, ..., etc.), and stores it for deduping 284 | func (s *standardTableCreator) newMConfigurationName(name string) string { 285 | if s.mConfigurationNames == nil { 286 | s.mConfigurationNames = map[string]string{} 287 | s.nameCount++ 288 | } 289 | newName, ok := s.mConfigurationNames[name] 290 | if !ok { 291 | newName = mConfigurationNamePrefix + strconv.Itoa(s.nameCount) 292 | s.nameCount++ 293 | s.mConfigurationNames[name] = newName 294 | } 295 | return newName 296 | } 297 | 298 | // Returns a new standardized m-configuration name (of the form q1, q2, ..., etc.), without storing it 299 | func (s *standardTableCreator) newHiddenMConfigurationName() string { 300 | if s.mConfigurationNames == nil { 301 | s.mConfigurationNames = map[string]string{} 302 | s.nameCount++ 303 | } 304 | newName := mConfigurationNamePrefix + strconv.Itoa(s.nameCount) 305 | s.nameCount++ 306 | return newName 307 | } 308 | 309 | // Returns the standardized symbol name (of the form S0, S1, ..., etc.), and stores it for deduping 310 | func (s *standardTableCreator) newMConfigurationSymbol(symbol string) string { 311 | if s.mConfigurationSymbols == nil { 312 | s.mConfigurationSymbols = map[string]string{} 313 | } 314 | newSymbol, ok := s.mConfigurationSymbols[symbol] 315 | if !ok { 316 | newSymbol = mConfigurationSymbolPrefix + strconv.Itoa(s.symbolCount) 317 | s.symbolCount++ 318 | s.mConfigurationSymbols[symbol] = newSymbol 319 | } 320 | return newSymbol 321 | } 322 | 323 | // Returns the full set of new symbols in a slice 324 | func (s *standardTableCreator) newMConfigurationSymbols() []string { 325 | symbols := []string{} 326 | for _, v := range s.mConfigurationSymbols { 327 | symbols = append(symbols, v) 328 | } 329 | return symbols 330 | } 331 | 332 | // Returns a map from new symbols to old symbols 333 | func (s *standardTableCreator) reverseMConfigurationSymbols() SymbolMap { 334 | mConfigurationSymbols := SymbolMap{} 335 | for k, v := range s.mConfigurationSymbols { 336 | mConfigurationSymbols[v] = k 337 | } 338 | return mConfigurationSymbols 339 | } 340 | 341 | // Returns the starting m-configuration for the standardize machine 342 | func (s *standardTableCreator) newStartingMConfiguration() string { 343 | if len(s.input.StartingMConfiguration) == 0 { 344 | return "" 345 | } else { 346 | return s.newMConfigurationName(s.input.StartingMConfiguration) 347 | } 348 | } 349 | 350 | // Returns a standardized tape for the machine 351 | func (s *standardTableCreator) newTape() []string { 352 | newTape := []string{} 353 | for _, square := range s.input.Tape { 354 | newTape = append(newTape, s.mConfigurationSymbols[square]) 355 | } 356 | return newTape 357 | } 358 | 359 | // Translates a tape to the original symbol set. 360 | func (sm SymbolMap) TranslateTape(tape Tape) string { 361 | var translatedTape strings.Builder 362 | for _, square := range tape { 363 | translatedTape.WriteString(sm[square]) 364 | } 365 | return translatedTape.String() 366 | } 367 | 368 | // Converts a StandardTable to its StandardDescription (S.D.) 369 | func toStandardDescription(input MachineInput) StandardDescription { 370 | var standardDescription strings.Builder 371 | for _, standardMConfiguration := range input.MConfigurations { 372 | // There is a bug in original paper, each m-configuration should begin with a semi-colon. 373 | standardDescription.WriteByte(semicolon) 374 | 375 | // Name is `DAAA` 376 | standardDescription.WriteByte(d) 377 | nameSuffix := standardMConfiguration.Name[1:] 378 | nameNum, _ := strconv.Atoi(nameSuffix) 379 | standardDescription.Write(bytes.Repeat([]byte{a}, nameNum)) 380 | 381 | // Symbol is `DCCC` 382 | standardDescription.WriteByte(d) 383 | symbolSuffix := standardMConfiguration.Symbols[0][1:] 384 | symbolNum, _ := strconv.Atoi(symbolSuffix) 385 | standardDescription.Write(bytes.Repeat([]byte{c}, symbolNum)) 386 | 387 | // Print is also is `DCCC` 388 | standardDescription.WriteByte(d) 389 | printOperationSuffix := standardMConfiguration.Operations[0][2:] 390 | printOperationNum, _ := strconv.Atoi(printOperationSuffix) 391 | standardDescription.Write(bytes.Repeat([]byte{c}, printOperationNum)) 392 | 393 | // Move Operations is `L`, `R`, or `N` 394 | standardDescription.WriteString(standardMConfiguration.Operations[1]) 395 | 396 | // Final Configuration is also `DAAA` 397 | standardDescription.WriteByte(d) 398 | finalMConfigurationSuffix := standardMConfiguration.FinalMConfiguration[1:] 399 | finalMConfigurationNum, _ := strconv.Atoi(finalMConfigurationSuffix) 400 | standardDescription.Write(bytes.Repeat([]byte{a}, finalMConfigurationNum)) 401 | } 402 | 403 | return StandardDescription(standardDescription.String()) 404 | } 405 | 406 | // Conversts a S.D. to a D.N. 407 | func toDescriptionNumber(sd StandardDescription) DescriptionNumber { 408 | var descriptionNumber strings.Builder 409 | for _, char := range []byte(sd) { 410 | descriptionNumber.WriteString(strconv.Itoa(sdCharToDNInt[char])) 411 | } 412 | return DescriptionNumber(descriptionNumber.String()) 413 | } 414 | 415 | // Converts a D.N. to a Machine. Returns an error if the D.N. is not well-defined. 416 | func NewMachineFromDescriptionNumber(dn DescriptionNumber) (MachineInput, error) { 417 | matched, _ := regexp.MatchString("^(?:731+32*32*[456]31+)+$", string(dn)) 418 | if !matched { 419 | return MachineInput{}, errors.New("not a well defined Description Number") 420 | } 421 | 422 | var standardDescription strings.Builder 423 | for _, char := range []byte(dn) { 424 | i, err := strconv.Atoi(string(char)) 425 | if err != nil { 426 | return MachineInput{}, err 427 | } 428 | standardDescription.WriteString(string(dnIntToSDChar[i])) 429 | } 430 | 431 | mConfigurations := []MConfiguration{} 432 | for _, section := range strings.Split(standardDescription.String()[1:], string(semicolon)) { 433 | subsections := strings.Split(section[1:], string(d)) 434 | name := mConfigurationNamePrefix + strconv.Itoa(len(subsections[0])) 435 | symbol := mConfigurationSymbolPrefix + strconv.Itoa(len(subsections[1])) 436 | printOperation := string(printOp) + mConfigurationSymbolPrefix + strconv.Itoa(len(subsections[2])-1) 437 | moveOperation := string(subsections[2][len(subsections[2])-1]) 438 | finalMConfiguration := mConfigurationNamePrefix + strconv.Itoa(len(subsections[len(subsections)-1])) 439 | 440 | mConfigurations = append(mConfigurations, MConfiguration{ 441 | Name: name, 442 | Symbols: []string{symbol}, 443 | Operations: []string{printOperation, moveOperation}, 444 | FinalMConfiguration: finalMConfiguration, 445 | }) 446 | } 447 | 448 | possibleSymbols := []string{} 449 | for i := 0; i <= maxCharsRepeated([]byte(standardDescription.String()), c); i++ { 450 | possibleSymbols = append(possibleSymbols, mConfigurationSymbolPrefix+strconv.Itoa(i)) 451 | } 452 | 453 | return MachineInput{ 454 | MConfigurations: mConfigurations, 455 | PossibleSymbols: possibleSymbols, 456 | NoneSymbol: mConfigurationSymbolPrefix + strconv.Itoa(0), 457 | }, nil 458 | } 459 | 460 | func maxCharsRepeated(s []byte, ch byte) int { 461 | var maxCount int 462 | var runningCount int 463 | for _, b := range s { 464 | if b == ch { 465 | runningCount += 1 466 | if runningCount > maxCount { 467 | maxCount = runningCount 468 | } 469 | } else { 470 | runningCount = 0 471 | } 472 | } 473 | return maxCount 474 | } 475 | -------------------------------------------------------------------------------- /standard_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStandardMachineExample1(t *testing.T) { 8 | st := NewStandardTable(MachineInput{ 9 | MConfigurations: []MConfiguration{ 10 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 11 | {"c", []string{" "}, []string{"R"}, "e"}, 12 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 13 | {"k", []string{" "}, []string{"R"}, "b"}, 14 | }, 15 | PossibleSymbols: []string{"0", "1"}, 16 | }) 17 | m := NewMachine(st.MachineInput) 18 | m.MoveN(100) 19 | checkTape(t, st.SymbolMap.TranslateTape(m.Tape()), "0 1 0 1 0 1 0 1 0 1 0 1") 20 | checkStandardDescription(t, st.StandardDescription, ";DADDCRDAA;DAADDRDAAA;DAAADDCCRDAAAA;DAAAADDRDA") 21 | checkDescriptionNumber(t, st.DescriptionNumber, "73133253117311335311173111332253111173111133531") 22 | } 23 | 24 | func TestStandardMachineExample1Short(t *testing.T) { 25 | st := NewStandardTable(MachineInput{ 26 | MConfigurations: []MConfiguration{ 27 | {"b", []string{" "}, []string{"P0"}, "b"}, 28 | {"b", []string{"0"}, []string{"R", "R", "P1"}, "b"}, 29 | {"b", []string{"1"}, []string{"R", "R", "P0"}, "b"}, 30 | }, 31 | PossibleSymbols: []string{"0", "1"}, 32 | }) 33 | m := NewMachine(st.MachineInput) 34 | m.MoveN(100) 35 | checkTape(t, st.SymbolMap.TranslateTape(m.Tape()), "0 1 0 1 0 1 0 1 0 1 0 1") 36 | // No StandardDescription or DescriptionNumner given 37 | } 38 | 39 | func TestStandardMachineExample2(t *testing.T) { 40 | st := NewStandardTable(MachineInput{ 41 | MConfigurations: []MConfiguration{ 42 | {"b", []string{"*", " "}, []string{"Pe", "R", "Pe", "R", "P0", "R", "R", "P0", "L", "L"}, "o"}, 43 | {"o", []string{"1"}, []string{"R", "Px", "L", "L", "L"}, "o"}, 44 | {"o", []string{"0"}, []string{}, "q"}, 45 | {"q", []string{"0", "1"}, []string{"R", "R"}, "q"}, 46 | {"q", []string{" "}, []string{"P1", "L"}, "p"}, 47 | {"p", []string{"x"}, []string{"E", "R"}, "q"}, 48 | {"p", []string{"e"}, []string{"R"}, "f"}, 49 | {"p", []string{" "}, []string{"L", "L"}, "p"}, 50 | {"f", []string{"*"}, []string{"R", "R"}, "f"}, 51 | {"f", []string{" "}, []string{"P0", "L", "L"}, "o"}, 52 | }, 53 | PossibleSymbols: []string{"0", "1", "e", "x"}, 54 | }) 55 | m := NewMachine(st.MachineInput) 56 | m.MoveN(1000) 57 | checkTape(t, st.SymbolMap.TranslateTape(m.Tape()), "ee0 0 1 0 1 1 0 1 1 1 0 1 1 1 1") 58 | // No StandardDescription or DescriptionNumner given 59 | } 60 | 61 | func TestNewMachineFromDescriptionNumber(t *testing.T) { 62 | st := NewStandardTable(MachineInput{ 63 | MConfigurations: []MConfiguration{ 64 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 65 | {"c", []string{" "}, []string{"R"}, "e"}, 66 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 67 | {"k", []string{" "}, []string{"R"}, "b"}, 68 | }, 69 | PossibleSymbols: []string{"0", "1"}, 70 | }) 71 | m := NewMachine(st.MachineInput) 72 | m.MoveN(100) 73 | 74 | newMachineInput, err := NewMachineFromDescriptionNumber(st.DescriptionNumber) 75 | if err != nil { 76 | t.Error(err) 77 | } 78 | newM := NewMachine(newMachineInput) 79 | newM.MoveN(100) 80 | 81 | checkTape(t, m.TapeString(), newM.TapeString()) 82 | } 83 | 84 | func checkStandardDescription(t *testing.T, actual StandardDescription, expected string) { 85 | if string(actual) != expected { 86 | t.Errorf("got %s, want %s", actual, expected) 87 | } 88 | } 89 | 90 | func checkDescriptionNumber(t *testing.T, actual DescriptionNumber, expected string) { 91 | if string(actual) != expected { 92 | t.Errorf("got %s, want %s", actual, expected) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /universal.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // The possible symbols for the universal machine 11 | possibleSymbolsForUniversalMachine = []string{ 12 | "e", "::", ":", "A", "C", "D", "L", "R", "N", ";", "0", "1", "u", "v", "w", "x", "y", "z", 13 | } 14 | 15 | // Note: By convention any symbol prefixed with an underscore (i.e. `_y`) is a 16 | // 'symbol parameter'. Similarly, there is one location (`con2`) where MFunction 17 | // parameters and symbols overlap (in the paper Turing uses capital German letters 18 | // for MFunction variables, but I chose to keep english.) In this case I also use 19 | // prefix the variable with an underscore. 20 | 21 | // `con(C, a)`. Starting from an F-square, S say, the sequence C of symbols describing 22 | // a configuration closest on the right of S is marked out with letters a. -> `C` 23 | configuration = []MConfiguration{ 24 | {"con(C, a)", []string{"!A", " "}, []string{"R", "R"}, "con(C, a)"}, 25 | {"con(C, a)", []string{"A"}, []string{"L", "Pa", "R"}, "con1(C, a)"}, 26 | {"con1(C, a)", []string{"A"}, []string{"R", "Pa", "R"}, "con1(C, a)"}, 27 | {"con1(C, a)", []string{"D"}, []string{"R", "Pa", "R"}, "con2(C, a)"}, 28 | // Post suggests this final `con1` line is missing from the original paper 29 | {"con1(C, a)", []string{" "}, []string{"PD", "R", "Pa", "R", "R", "R"}, "C"}, 30 | {"con2(_C, a)", []string{"C"}, []string{"R", "Pa", "R"}, "con2(_C, a)"}, 31 | {"con2(_C, a)", []string{"!C", " "}, []string{"R", "R"}, "_C"}, 32 | } 33 | 34 | // `b`. The machine prints `:`, `D`, `A` on the F-squares after `::` -> `anf`. 35 | begin = []MConfiguration{ 36 | {"b", []string{"*", " "}, []string{}, "f(b1, b1, ::)"}, 37 | {"b1", []string{"*", " "}, []string{"R", "R", "P:", "R", "R", "PD", "R", "R", "PA"}, "anf"}, 38 | } 39 | 40 | // `anf`. The machine marks the configuration in the last complete configuration with `y`. -> `kom` 41 | anfang = []MConfiguration{ 42 | {"anf", []string{"*", " "}, []string{}, "g(anf1, :)"}, 43 | {"anf1", []string{"*", " "}, []string{}, "con(kom, y)"}, 44 | } 45 | 46 | // `kom`. The machine finds the last semi-colon not marked with `z`. It marks this semi-colon 47 | // with `z` and the configuration following it with `x`. 48 | kom = []MConfiguration{ 49 | {"kom", []string{";"}, []string{"R", "Pz", "L"}, "con(kmp, x)"}, 50 | {"kom", []string{"z"}, []string{"L", "L"}, "kom"}, 51 | {"kom", []string{"!z", "!;", " "}, []string{"L"}, "kom"}, 52 | } 53 | 54 | // `kmp`. The machine compares the sequences marked `x` and `y`. It erases all letters 55 | // `x` and `y`. -> `sim` if they are alike. Otherwise -> `kom`. 56 | kmp = []MConfiguration{ 57 | {"kmp", []string{"*", " "}, []string{}, "cpe(e(e(anf, x), y), sim, x, y)"}, 58 | } 59 | 60 | // `sim`. The machine marks out the instructions. That part of the instructions 61 | // which refers to operations to be carried out is marked with `u`, and the final 62 | // MConfiguration with `y`. The letters `z` are erased. 63 | similar = []MConfiguration{ 64 | {"sim", []string{"*", " "}, []string{}, "fl(sim1, sim1, z)"}, 65 | {"sim1", []string{"*", " "}, []string{}, "con(sim2, )"}, 66 | {"sim2", []string{"A"}, []string{}, "sim3"}, 67 | // Post suggests this final `sim2` line should move left before printing `u` 68 | {"sim2", []string{"!A", " "}, []string{"L", "Pu", "R", "R", "R"}, "sim2"}, 69 | {"sim3", []string{"!A", " "}, []string{"L", "Py"}, "e(mk, z)"}, 70 | {"sim3", []string{"A"}, []string{"L", "Py", "R", "R", "R"}, "sim3"}, 71 | } 72 | 73 | // `mk`. The last complete configuration is marked out into four sections. The 74 | // configuration is left unmarked. The symbol directly preceding it is marked 75 | // with `x`. The remainder of the complete configuration is divided into two 76 | // parts, of which the first is marked with `v` and the last with `w`. A colon 77 | // is printed after the whole. -> `sh`. 78 | mark = []MConfiguration{ 79 | {"mk", []string{"*", " "}, []string{}, "g(mk1, :)"}, 80 | {"mk1", []string{"!A", " "}, []string{"R", "R"}, "mk1"}, 81 | {"mk1", []string{"A"}, []string{"L", "L", "L", "L"}, "mk2"}, 82 | {"mk2", []string{"C"}, []string{"R", "Px", "L", "L", "L"}, "mk2"}, 83 | {"mk2", []string{":"}, []string{}, "mk4"}, 84 | {"mk2", []string{"D"}, []string{"R", "Px", "L", "L", "L"}, "mk3"}, 85 | {"mk3", []string{"!:", " "}, []string{"R", "Pv", "L", "L", "L"}, "mk3"}, 86 | {"mk3", []string{":"}, []string{}, "mk4"}, 87 | {"mk4", []string{"*", " "}, []string{}, "con(l(l(mk5)), )"}, 88 | {"mk5", []string{"*"}, []string{"R", "Pw", "R"}, "mk5"}, 89 | {"mk5", []string{" "}, []string{"P:"}, "sh"}, 90 | } 91 | 92 | // `sh`. The instructions (marked `u`) are examined. If it is found that they involve 93 | // "Print 1", then `0`, `:` or `1`, `:` is printed at the end. 94 | // Note: See `enhancedShow` for printing symbols beyong binary digits. 95 | show = []MConfiguration{ 96 | {"sh", []string{"*", " "}, []string{}, "f(sh1, inst, u)"}, 97 | {"sh1", []string{"*", " "}, []string{"L", "L", "L"}, "sh2"}, 98 | {"sh2", []string{"D"}, []string{"R", "R", "R", "R"}, "sh3"}, 99 | {"sh2", []string{"!D", " "}, []string{}, "inst"}, 100 | {"sh3", []string{"C"}, []string{"R", "R"}, "sh4"}, 101 | {"sh3", []string{"!C", " "}, []string{}, "inst"}, 102 | {"sh4", []string{"C"}, []string{"R", "R"}, "sh5"}, 103 | {"sh4", []string{"!C", " "}, []string{}, "pe2(inst, 0, :)"}, 104 | {"sh5", []string{"C"}, []string{}, "inst"}, 105 | {"sh5", []string{"!C", " "}, []string{}, "pe2(inst, 1, :)"}, 106 | } 107 | 108 | // `inst`. The next complete configuration is written down, carrying out the 109 | // marked instructions. The letters `u`, `v`, `w`, `x`, `y` are erased. -> `anf`. 110 | // Note that we are using Petzold's recommended inst1 which does not require new machine behavior. 111 | instruction = []MConfiguration{ 112 | {"inst", []string{"*", " "}, []string{}, "g(l(inst1), u)"}, 113 | {"inst1", []string{"L"}, []string{"R", "E"}, "ce5(ov, v, y, x, u, w)"}, 114 | {"inst1", []string{"R"}, []string{"R", "E"}, "ce5(ov, v, x, u, y, w)"}, 115 | {"inst1", []string{"N"}, []string{"R", "E"}, "ce5(ov, v, x, y, u, w)"}, 116 | {"ov", []string{"*", " "}, []string{}, "e(anf)"}, 117 | } 118 | ) 119 | 120 | type ( 121 | // Input for the UniversalMachine 122 | UniversalMachineInput struct { 123 | StandardDescription 124 | SymbolMap 125 | } 126 | ) 127 | 128 | // If `M` is a Machine that computes a sequence, this function takes the Standard Description of `M` and returns 129 | // MachineInput that will print `M`'s sequence using `U` (the Universal Machine). 130 | func NewUniversalMachine(input UniversalMachineInput) MachineInput { 131 | // Helper MFunctions 132 | mConfigurations := []MConfiguration{} 133 | mConfigurations = append(mConfigurations, allhelperFunctions()...) 134 | 135 | // Universal Machine MFunctions 136 | mConfigurations = append(mConfigurations, configuration...) 137 | mConfigurations = append(mConfigurations, begin...) 138 | mConfigurations = append(mConfigurations, anfang...) 139 | mConfigurations = append(mConfigurations, kom...) 140 | mConfigurations = append(mConfigurations, kmp...) 141 | mConfigurations = append(mConfigurations, similar...) 142 | mConfigurations = append(mConfigurations, mark...) 143 | mConfigurations = append(mConfigurations, getEnhancedShow(input.SymbolMap)...) 144 | mConfigurations = append(mConfigurations, instruction...) 145 | 146 | // Construct tape 147 | tapeFromStandardDescription := []string{"e", "e"} 148 | for _, char := range input.StandardDescription { 149 | tapeFromStandardDescription = append(tapeFromStandardDescription, string(char)) 150 | tapeFromStandardDescription = append(tapeFromStandardDescription, none) 151 | } 152 | tapeFromStandardDescription = append(tapeFromStandardDescription, "::") 153 | 154 | // Return MachineInput of the compiled abbreviated table of `U` 155 | return NewAbbreviatedTable(AbbreviatedTableInput{ 156 | MConfigurations: mConfigurations, 157 | Tape: tapeFromStandardDescription, 158 | StartingMConfiguration: "b", 159 | PossibleSymbols: possibleSymbolsForUniversalMachine, 160 | }) 161 | } 162 | 163 | // Rather than using Turing's original `show` m-function, we create our own version 164 | // that is capable of printing all characters the Machine requires (not just `0` and `1`). 165 | func getEnhancedShow(symbolMap SymbolMap) []MConfiguration { 166 | enhancedShow := []MConfiguration{} 167 | 168 | // First four `show` MConfigurations are valid 169 | enhancedShow = append(enhancedShow, show[0:4]...) 170 | 171 | // Pick up where `show` left off... 172 | for symbolKey, symbolValue := range symbolMap { 173 | symbolNumber, _ := strconv.Atoi(symbolKey[1:]) 174 | // The blank symbol (S0) is `sh3`, and so on. 175 | showNumber := symbolNumber + 3 176 | showName := fmt.Sprintf("sh%d", showNumber) 177 | 178 | // To continue our `enhancedShow` MConfigurations move to the next afterwards, 179 | // unless it is the last symbol. 180 | var nextShowName string 181 | if symbolNumber >= len(symbolMap)-1 { 182 | nextShowName = "inst" 183 | } else { 184 | nextShowName = fmt.Sprintf("sh%d", showNumber+1) 185 | } 186 | 187 | // Turing's convention is that F-squares do not contain blanks 188 | // unless they are the end of the tape. This poses a problem for our 189 | // `enhancedShow` MFunction, which would like to show blanks, etc. 190 | // In addition, sometimes we might be simulating a Machine that prints 191 | // `e`, etc. or other characters that the Universal Machine itself uses. 192 | // To combat this, we prepend "shown" values with `_` (underscore). 193 | // The `CondensedTapeString` will remove this prepended underscore. 194 | printSymbol := fmt.Sprintf("pe2(inst, _%s, :)", symbolValue) 195 | 196 | enhancedShow = append(enhancedShow, []MConfiguration{ 197 | {showName, []string{"C"}, []string{"R", "R"}, nextShowName}, 198 | {showName, []string{"!C", " "}, []string{}, printSymbol}, 199 | }...) 200 | } 201 | 202 | return enhancedShow 203 | } 204 | 205 | // Helper function to isolate the computed sequence between the colons 206 | func (m *Machine) TapeStringFromUniversalMachine() string { 207 | var tapeString strings.Builder 208 | 209 | // We essentially need to find only the symbols between two colons 210 | var started bool 211 | var skip bool 212 | var squareMinusTwo string 213 | var squareMinusOne string 214 | for _, square := range m.tape { 215 | if !started { 216 | if square == "::" { 217 | started = true 218 | skip = true 219 | } 220 | continue 221 | } 222 | if skip { 223 | skip = !skip 224 | continue 225 | } 226 | if squareMinusTwo == ":" && square == ":" { 227 | if squareMinusOne == "_" { 228 | tapeString.WriteString(none) 229 | } else { 230 | tapeString.WriteString(strings.TrimPrefix(squareMinusOne, "_")) 231 | } 232 | } 233 | squareMinusTwo = squareMinusOne 234 | squareMinusOne = square 235 | skip = !skip 236 | } 237 | return tapeString.String() 238 | } 239 | -------------------------------------------------------------------------------- /universal_test.go: -------------------------------------------------------------------------------- 1 | package turing 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUniversalMachineExample1(t *testing.T) { 8 | input := MachineInput{ 9 | MConfigurations: []MConfiguration{ 10 | {"b", []string{" "}, []string{"P0", "R"}, "c"}, 11 | {"c", []string{" "}, []string{"R"}, "e"}, 12 | {"e", []string{" "}, []string{"P1", "R"}, "k"}, 13 | {"k", []string{" "}, []string{"R"}, "b"}, 14 | }, 15 | } 16 | 17 | m := NewMachine(input) 18 | st := NewStandardTable(input) 19 | 20 | expected := "0 1 0 1 0 1" 21 | 22 | // Check Machine Tape 23 | m.MoveN(50) 24 | checkTape(t, m.TapeString(), expected) 25 | 26 | // Check Universal Machine Tape 27 | um := NewMachine(NewUniversalMachine(UniversalMachineInput{ 28 | StandardDescription: st.StandardDescription, 29 | SymbolMap: st.SymbolMap, 30 | })) 31 | um.MoveN(500000) 32 | checkTape(t, um.TapeStringFromUniversalMachine(), expected) 33 | } 34 | --------------------------------------------------------------------------------