-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmachine.go
292 lines (244 loc) · 8.19 KB
/
machine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
package turing
import (
"fmt"
"slices"
"strings"
)
type (
// We may compare a man in the process of computing a real number to a machine...
MachineInput struct {
// ...which is only capable of a finite number of conditions q1, q2, ..., qR which
// will be called "m-configurations".
MConfigurations []MConfiguration
// The machine is supplied with a "tape" (the analogue of paper) running through it,
// and divided into sections (called "squares") each capable of bearing a "symbol".
Tape Tape
// The m-configuration that the machine should start with. If empty the first m-configuration
// in the list is chosen.
StartingMConfiguration string
// A list of all symbols the machine is capable of reading or printing.
// This field is used when dealing with special symbols `*` (Any), `!` (Not)
// Note: The ` ` (None) symbol does not have to be provided (it is assumed).
PossibleSymbols []string
// Defaults to ` ` (None), but can optionally be overridden here.
NoneSymbol string
// If `true`, the machine's complete configurations are printed at the end of each move.
Debug bool
}
// Turing's Machine
Machine struct {
// See corresponding input field
mConfigurations []MConfiguration
// See corresponding input field
tape []string
// See corresponding input field
possibleSymbols []string
// See corresponding input field
noneSymbol string
// See corresponding input field
debug bool
// At any moment there is just one square, say the r-th, bearing the symbol S(r)
// which is "in the machine". We may call this square the "scanned square".
// The symbol on the scanned square may be called the "scanned symbol".
// The "scanned symbol" is the only one of which the machine is, so to speak, "directly aware".
scannedSquare int
// The current m-configuration of the machine.
currentMConfigurationName string
// Stores whether the machine has "halted" or not. A machine only halts if it cannot
// find an m-configuration.
halted bool
}
// An m-configuration contains four components
MConfiguration struct {
// The possible behaviour of the machine at any moment is determined by the m-configuration qn...
Name string
// ...and the scanned symbol S(r)
Symbols []string
// In some of the configurations in which the scanned square is blank (i.e. bears no symbol)
// the machine writes down a new symbol on the scanned square: in other configurations it
// erases the scanned symbol. The machine may also change the square which is being scanned,
// but only by shifting it one place to right or left.
Operations []string
// In addition to any of these operations the m-configuration may be changed.
FinalMConfiguration string
}
// Our "tape" is a slice of strings because squares can contain multiple characters
Tape []string
// Well-known single-character codes used in an m-configuration's operations.
operationCode byte
)
const (
rightOp operationCode = 'R'
leftOp operationCode = 'L'
eraseOp operationCode = 'E'
printOp operationCode = 'P'
none string = " "
not string = "!"
any string = "*"
)
// Returns a new Machine
func NewMachine(input MachineInput) *Machine {
m := &Machine{
mConfigurations: input.MConfigurations,
debug: input.Debug,
}
// Use first m-configuration if starting m-configuration not specified
if len(input.StartingMConfiguration) == 0 {
m.currentMConfigurationName = input.MConfigurations[0].Name
} else {
m.currentMConfigurationName = input.StartingMConfiguration
}
// Use default None character if not specified
if len(input.NoneSymbol) == 0 {
m.noneSymbol = none
} else {
m.noneSymbol = input.NoneSymbol
}
// Initialize tape if nil
if input.Tape == nil {
m.tape = []string{}
} else {
m.tape = input.Tape
}
if m.debug {
m.printMConfigurationsForDebug()
}
return m
}
// Moves the machine n times and stops early if halted. Returns the amount of moves the machine took.
func (m *Machine) MoveN(n int) int {
for i := 1; i <= n; i++ {
m.Move()
if m.halted {
return i
}
}
return n
}
// Moves the machine once
func (m *Machine) Move() {
if m.halted {
return
}
// Scan symbol from the tape
symbol := m.scan()
// Find the the correct m-configuration depending on the scanned synbol
mConfiguration, shouldHalt := m.findMConfiguration(m.currentMConfigurationName, symbol)
// If an m-configuration could not be found, halt the machine
if shouldHalt {
m.halted = true
return
}
// Perform operations
for _, operation := range mConfiguration.Operations {
m.performOperation(operation)
}
if m.debug {
m.printCompleteConfigurationForDebug()
}
// Move to specified final-m-configuration
m.currentMConfigurationName = mConfiguration.FinalMConfiguration
}
// Returns the Machine's Tape
func (m *Machine) Tape() Tape {
return m.tape
}
// Return the Tape represented as a string
func (m *Machine) TapeString() string {
return strings.Join([]string(m.tape), "")
}
// Returns the machine's Complete Configuration of the single-line form
func (m *Machine) CompleteConfiguration() string {
var completeConfiguration strings.Builder
for i, square := range m.tape {
if i == m.scannedSquare {
completeConfiguration.WriteString(m.currentMConfigurationName)
}
completeConfiguration.WriteString(square)
}
if m.scannedSquare == len(m.tape) {
completeConfiguration.WriteString(m.currentMConfigurationName)
}
return completeConfiguration.String()
}
// Scans the tape for the scanned symbol
func (m *Machine) scan() string {
m.extendTapeIfNeeded()
return m.tape[m.scannedSquare]
}
// The Machine's Tape is infinite, so we extend it as-needed
func (m *Machine) extendTapeIfNeeded() {
if m.scannedSquare >= len(m.tape) {
m.tape = append(m.tape, m.noneSymbol)
}
if m.scannedSquare < 0 {
m.tape = append([]string{m.noneSymbol}, m.tape...)
m.scannedSquare++
}
}
// Find the appropriate full m-configuration given the current m-configuration name and the scanned symbol
func (m *Machine) findMConfiguration(mConfigurationName string, symbol string) (MConfiguration, bool) {
for _, mConfiguration := range m.mConfigurations {
if mConfiguration.Name == mConfigurationName {
// Scenario 1: The provided symbol is contained exactly in the m-configuration
if slices.Contains(mConfiguration.Symbols, symbol) {
return mConfiguration, false
}
if symbol != m.noneSymbol {
// Scenario 2: The m-configuration contains `*`
// Note that `*` does not include ` ` (None), which must be specified manually
if slices.Contains(mConfiguration.Symbols, any) {
return mConfiguration, false
}
// Scenario 3: The MConfiguration contains `!x` where `x` is not the provided symbol
// Note that `!` does not include ` ` (None), which must be specified manually
notSymbols := []string{}
// First loop is required in the scenario we have multiple (`!x` and `!y`)
for _, mConfigurationSymbol := range mConfiguration.Symbols {
if strings.Contains(mConfigurationSymbol, not) {
notSymbols = append(notSymbols, mConfigurationSymbol[1:])
}
}
if len(notSymbols) > 0 && !slices.Contains(notSymbols, symbol) {
return mConfiguration, false
}
}
}
}
return MConfiguration{}, true
}
// Perform an operation
func (m *Machine) performOperation(operation string) {
m.extendTapeIfNeeded()
switch operationCode(operation[0]) {
case rightOp:
m.scannedSquare++
case leftOp:
m.scannedSquare--
case eraseOp:
m.tape[m.scannedSquare] = m.noneSymbol
case printOp:
m.tape[m.scannedSquare] = string(operation[1:])
}
}
// Prints the m-configurations of the machine nicely for debugging
func (m *Machine) printMConfigurationsForDebug() {
for _, mConfiguration := range m.mConfigurations {
fmt.Printf("%s %v %v %s\n", mConfiguration.Name, mConfiguration.Symbols, mConfiguration.Operations, mConfiguration.FinalMConfiguration)
}
}
// Prints the complete configuration for the machine nicely for debugging
func (m *Machine) printCompleteConfigurationForDebug() {
for _, square := range m.tape {
fmt.Print(strings.Repeat("-", len(square)))
}
fmt.Println("-")
fmt.Println(m.TapeString())
for i, square := range m.tape {
if i >= m.scannedSquare {
break
}
fmt.Print(strings.Repeat(" ", len(square)))
}
fmt.Println(m.currentMConfigurationName)
}