-
Notifications
You must be signed in to change notification settings - Fork 0
/
mathison.rb
192 lines (160 loc) · 5.06 KB
/
mathison.rb
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
require 'pp'
require 'byebug'
class EmbeddedString
attr_accessor :string
attr_accessor :total_len
attr_accessor :zero_point
attr_accessor :mutated_range
def initialize(string)
len = string.length
@string = [(' ' * len), string, (' ' * len)].join(' ')
@zero_point = len + 1
@total_len = @string.length
@mutated_range = (@zero_point..(@zero_point + len))
end
def get(index)
true_index = index + @zero_point
if(true_index < 0 || true_index > @total_len)
' '
else
@string[true_index]
end
end
def set(index, value)
true_index = index + @zero_point
while(true_index < 0 || true_index > @total_len)
len = @string.length
@string = [(' ' * len), string, (' ' * len)].join(' ')
@zero_point += len + 1
@total_len = @string.length
true_index = index + @zero_point
end
@string[true_index] = value
if(true_index < @mutated_range.min)
@mutated_range = (true_index..(@mutated_range.max))
end
if(true_index > @mutated_range.max)
@mutated_range = ((@mutated_range.min)..true_index)
end
end
def stringify
@string[@mutated_range]
end
end
class TuringMachine
attr_accessor :fname
attr_accessor :state
attr_accessor :states
attr_accessor :tape
attr_accessor :head_index
attr_accessor :halted
def perror(msg, lineno = nil)
if lineno.nil?
raise "TM error in #{@fname}: #{msg}"
else
raise "TM error at #{@fname}:#{lineno}: #{msg}"
end
end
def pwarning(msg, lineno = nil)
if lineno.nil?
STDERR.puts "\033[1mTM warning\033[0m in #{@fname}: #{msg}"
else
STDERR.puts "\033[1mTM warning\033[0m at #{@fname}:#{lineno}: #{msg}"
end
end
def initialize(fname)
@halted = false
@states = { }
@fname = fname
state_index = nil
File.readlines(@fname, chomp: true).each_with_index do |line, i|
case line
when /^ *#/ # comment
# ignore, next line
when /^:(.*):\s*(#.*)?$/
state_index = $1
perror('illegal use of reserved state H', i + 1) if state_index =~ /^H$/
@state = state_index if @states.empty?
when / *([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/\s]+)\s*(#.*)?$/
pattern = /#{$1}/
replacement = $2
movement = $3
newstate = $4.chomp
perror('tried to set rule outside state context', i + 1) unless state_index
perror("illegal movement instruction \"#{movement}\"", i + 1) unless movement =~ /[LNR]/
@states[state_index] = {} unless @states[state_index]
@states[state_index][pattern] = { replacement: replacement,
movement: movement,
newstate: newstate }
else
perror("failed to parse #{line.dump}", i + 1)
end
end
legal_states = @states.keys.uniq
possible_states = @states.map { |_key, state| state.map { |_symbol, transition| transition[:newstate] } }.flatten.uniq
undefined_states = possible_states - legal_states
undefined_without_halt = undefined_states - ['H']
if undefined_without_halt.any?
pwarning("defined transitions to undefined states (#{undefined_without_halt.map { |state_name| "\"#{state_name}\"" } .join(', ')})")
end
end
def init_tape(tape, head_index)
@tape = EmbeddedString.new(tape)
@head_index = head_index
end
def halt
@state = 'H'
@halted = true
end
def iterate
symbol = @tape.get(@head_index)
has_matched = false
halt if @state =~ /^H$/
return if halted
perror("entered undefined state \"#{@state}\"") if @states[@state].nil?
@states[@state].each do |pattern, rule|
if(symbol =~ pattern)
has_matched = true
unless rule[:replacement] =~ /^&$/
@tape.set(@head_index, rule[:replacement])
end
if rule[:movement] =~ /L/
@head_index -= 1
elsif rule[:movement] =~ /R/
@head_index += 1
elsif rule[:movement] =~ /N/
else
raise 'TM runtime error'
end
if rule[:movement] =~ /N/ && @state == rule[:newstate] # Treat no motion, no state change as a halt
halt
else
@state = rule[:newstate]
end
break
end
end
perror("could not find matching rule for symbol #{symbol.dump}") unless has_matched
end
end
# tm = TuringMachine.new('copier.tur')
# tm.init_tape(" -#{(1..10).map{%w[0 1].sample}.join('')}- ", 1)
# tm = TuringMachine.new('beaver_4state.tur')
# tm.init_tape('0' * 32, 15)
tm = TuringMachine.new('bindec.tur')
tm.init_tape(" #{(1..10).map{%w[0 1].sample}.join('')} ", 1)
# pp tm.states
tempstr = tm.tape.stringify + ' '
# tempstr.insert(tm.head_index, "\e[7m")
# tempstr.insert(tm.head_index + 5, "\e[0m")
tempstr[tm.head_index] = "\e[7m#{tempstr[tm.head_index]}\e[0m"
puts "\e[2K#{tempstr}"
until tm.halted
tm.iterate
tempstr = tm.tape.stringify + ' '
# tempstr.insert(tm.head_index, "\e[7m")
# tempstr.insert(tm.head_index + 5, "\e[0m")
tempstr[tm.head_index] = "\e[7m#{tempstr[tm.head_index]}\e[0m"
puts "\e[2K#{tempstr}"
end
# test