|
0 |
#!/usr/local/bin/lua
|
|
1 |
--
|
|
2 |
-- beturing.lua
|
|
3 |
-- A Befunge-flavoured Turing(-esque) machine
|
|
4 |
-- Implemented in Lua 5 by Chris Pressey, June 2005
|
|
5 |
--
|
|
6 |
|
|
7 |
--
|
|
8 |
-- Copyright (c)2005 Cat's Eye Technologies. All rights reserved.
|
|
9 |
--
|
|
10 |
-- Redistribution and use in source and binary forms, with or without
|
|
11 |
-- modification, are permitted provided that the following conditions
|
|
12 |
-- are met:
|
|
13 |
--
|
|
14 |
-- Redistributions of source code must retain the above copyright
|
|
15 |
-- notice, this list of conditions and the following disclaimer.
|
|
16 |
--
|
|
17 |
-- Redistributions in binary form must reproduce the above copyright
|
|
18 |
-- notice, this list of conditions and the following disclaimer in
|
|
19 |
-- the documentation and/or other materials provided with the
|
|
20 |
-- distribution.
|
|
21 |
--
|
|
22 |
-- Neither the name of Cat's Eye Technologies nor the names of its
|
|
23 |
-- contributors may be used to endorse or promote products derived
|
|
24 |
-- from this software without specific prior written permission.
|
|
25 |
--
|
|
26 |
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
27 |
-- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
28 |
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
29 |
-- FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
30 |
-- COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
31 |
-- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
32 |
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
33 |
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
34 |
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
35 |
-- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
36 |
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
37 |
-- OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
38 |
--
|
|
39 |
-- $Id: beturing.lua 2 2005-06-06 22:28:23Z catseye $
|
|
40 |
|
|
41 |
--[[ Common functions ]]--
|
|
42 |
|
|
43 |
local debug_log = print
|
|
44 |
local usage = function()
|
|
45 |
io.stderr:write("Usage: [lua] beturing.lua [-q] [filename.bet]\n")
|
|
46 |
os.exit(1)
|
|
47 |
end
|
|
48 |
|
|
49 |
--[[ Object Classes ]]--
|
|
50 |
|
|
51 |
--[[-----------]]--
|
|
52 |
--[[ Playfield ]]--
|
|
53 |
--[[-----------]]--
|
|
54 |
|
|
55 |
--
|
|
56 |
-- Store an unbounded grid.
|
|
57 |
--
|
|
58 |
Playfield = {}
|
|
59 |
Playfield.new = function(tab)
|
|
60 |
tab = tab or {}
|
|
61 |
local nw, ne, sw, se = {}, {}, {}, {} -- quadrant storage
|
|
62 |
local min_x, min_y, max_x, max_y -- limits seen so far
|
|
63 |
local method = {}
|
|
64 |
|
|
65 |
--
|
|
66 |
-- Private function: pick the appropriate quadrant & translate
|
|
67 |
--
|
|
68 |
local pick_quadrant = function(x, y)
|
|
69 |
if x > 0 and y > 0 then return se, x, y end
|
|
70 |
if x > 0 and y <= 0 then return ne, x, 1-y end
|
|
71 |
if x <= 0 and y > 0 then return sw, 1-x, y end
|
|
72 |
if x <= 0 and y <= 0 then return nw, 1-x, 1-y end
|
|
73 |
end
|
|
74 |
|
|
75 |
--
|
|
76 |
-- Read the symbol at a given position in the playfield
|
|
77 |
--
|
|
78 |
method.peek = function(pf, x, y)
|
|
79 |
local contents, nx, ny = pick_quadrant(x, y)
|
|
80 |
contents[ny] = contents[ny] or {} -- make sure row exists
|
|
81 |
local sym = contents[ny][nx] or " "
|
|
82 |
return sym
|
|
83 |
end
|
|
84 |
|
|
85 |
--
|
|
86 |
-- Write a symbol at a given position in the playfield
|
|
87 |
--
|
|
88 |
method.poke = function(pf, x, y, sym)
|
|
89 |
local contents, nx, ny = pick_quadrant(x, y)
|
|
90 |
contents[ny] = contents[ny] or {} -- make sure row exists
|
|
91 |
contents[ny][nx] = sym
|
|
92 |
if not min_x or x < min_x then min_x = x end
|
|
93 |
if not max_x or x > max_x then max_x = x end
|
|
94 |
if not min_y or y < min_y then min_y = y end
|
|
95 |
if not max_y or y > max_y then max_y = y end
|
|
96 |
end
|
|
97 |
|
|
98 |
--
|
|
99 |
-- Store a string starting at (x, y).
|
|
100 |
--
|
|
101 |
method.poke_str = function(pf, x, y, str)
|
|
102 |
local i
|
|
103 |
for i = 1, string.len(str) do
|
|
104 |
pf:poke(x + (i - 1), y, string.sub(str, i, i))
|
|
105 |
end
|
|
106 |
end
|
|
107 |
|
|
108 |
--
|
|
109 |
-- Load the playfield from a file.
|
|
110 |
--
|
|
111 |
method.load = function(pf, filename, callback)
|
|
112 |
local file = io.open(filename)
|
|
113 |
local line = file:read("*l")
|
|
114 |
local x, y = 0, 0
|
|
115 |
|
|
116 |
while line do
|
|
117 |
if string.find(line, "^%s*%#") then
|
|
118 |
-- comment or directive - not included in playfield.
|
|
119 |
local found, len, nx, ny =
|
|
120 |
string.find(line, "^%s*%#%s*%@%(%s*(%-?%d+)%s*%,%s*(%-?%d+)%s*%)")
|
|
121 |
if found then
|
|
122 |
x = tonumber(nx)
|
|
123 |
y = tonumber(ny)
|
|
124 |
debug_log("Now loading at " ..
|
|
125 |
"(" .. tostring(x) .. "," .. tostring(y) .. ")")
|
|
126 |
else
|
|
127 |
callback(line)
|
|
128 |
end
|
|
129 |
else
|
|
130 |
pf:poke_str(x, y, line)
|
|
131 |
y = y + 1
|
|
132 |
end
|
|
133 |
line = file:read("*l")
|
|
134 |
end
|
|
135 |
file:close()
|
|
136 |
end
|
|
137 |
|
|
138 |
--
|
|
139 |
-- Return a string representing the playfield.
|
|
140 |
--
|
|
141 |
method.render = function(pf)
|
|
142 |
local y = min_y
|
|
143 |
local s = "--- (" .. tostring(min_x) .. "," .. tostring(min_y) .. ")-"
|
|
144 |
s = s .. "(" .. tostring(max_x) .. "," .. tostring(max_y) .. ") ---\n"
|
|
145 |
while y <= max_y do
|
|
146 |
local x = min_x
|
|
147 |
while x <= max_x do
|
|
148 |
s = s .. pf:peek(x, y)
|
|
149 |
x = x + 1
|
|
150 |
end
|
|
151 |
s = s .. "\n"
|
|
152 |
y = y + 1
|
|
153 |
end
|
|
154 |
|
|
155 |
return s
|
|
156 |
end
|
|
157 |
|
|
158 |
return method
|
|
159 |
end
|
|
160 |
|
|
161 |
--[[------]]--
|
|
162 |
--[[ Head ]]--
|
|
163 |
--[[------]]--
|
|
164 |
|
|
165 |
--
|
|
166 |
-- Represent a readable(/writeable) location within a playfield.
|
|
167 |
--
|
|
168 |
Head = {}
|
|
169 |
Head.new = function(tab)
|
|
170 |
tab = tab or {}
|
|
171 |
|
|
172 |
local pf = assert(tab.playfield)
|
|
173 |
local x = tab.x or 0
|
|
174 |
local y = tab.y or 0
|
|
175 |
|
|
176 |
local method = {}
|
|
177 |
|
|
178 |
method.read = function(hd, sym)
|
|
179 |
return pf:peek(x, y)
|
|
180 |
end
|
|
181 |
|
|
182 |
method.write = function(hd, sym)
|
|
183 |
pf:poke(x, y, sym)
|
|
184 |
end
|
|
185 |
|
|
186 |
--
|
|
187 |
-- look for this symbol -> 13 <- on match, write this symbol
|
|
188 |
-- on match, move head this way -> 24 <- choose next state on this
|
|
189 |
--
|
|
190 |
method.read_code = function(hd)
|
|
191 |
local seek_sym, repl_sym, move_cmd, state_cmd
|
|
192 |
|
|
193 |
debug_log("rd cd")
|
|
194 |
seek_sym = hd:read()
|
|
195 |
hd:move(">")
|
|
196 |
repl_sym = hd:read()
|
|
197 |
hd:move("v")
|
|
198 |
state_cmd = hd:read()
|
|
199 |
hd:move("<")
|
|
200 |
move_cmd = hd:read()
|
|
201 |
hd:move("^")
|
|
202 |
debug_log("cd rd")
|
|
203 |
|
|
204 |
return seek_sym, repl_sym, move_cmd, state_cmd
|
|
205 |
end
|
|
206 |
|
|
207 |
method.move = function(hd, sym)
|
|
208 |
if sym == "^" then
|
|
209 |
y = y - 1
|
|
210 |
elseif sym == "v" then
|
|
211 |
y = y + 1
|
|
212 |
elseif sym == "<" then
|
|
213 |
x = x - 1
|
|
214 |
elseif sym == ">" then
|
|
215 |
x = x + 1
|
|
216 |
elseif sym ~= "." then
|
|
217 |
error("Illegal movement symbol '" .. sym .. "'")
|
|
218 |
end
|
|
219 |
end
|
|
220 |
|
|
221 |
return method
|
|
222 |
end
|
|
223 |
|
|
224 |
--[[---------]]--
|
|
225 |
--[[ Machine ]]--
|
|
226 |
--[[---------]]--
|
|
227 |
|
|
228 |
--
|
|
229 |
-- Perform the mechanics of the machine.
|
|
230 |
--
|
|
231 |
Machine = {}
|
|
232 |
Machine.new = function(tab)
|
|
233 |
tab = tab or {}
|
|
234 |
|
|
235 |
local pf = tab.playfield or Playfield.new()
|
|
236 |
local data_head = Head.new{
|
|
237 |
playfield = pf,
|
|
238 |
x = tab.data_head_x or 0,
|
|
239 |
y = tab.data_head_y or 0
|
|
240 |
}
|
|
241 |
local code_head = Head.new{
|
|
242 |
playfield = pf,
|
|
243 |
x = tab.code_head_x or 0,
|
|
244 |
y = tab.code_head_y or 0
|
|
245 |
}
|
|
246 |
|
|
247 |
local method = {}
|
|
248 |
|
|
249 |
--
|
|
250 |
-- Private function: provide interpretation of the state-
|
|
251 |
-- transition operator.
|
|
252 |
--
|
|
253 |
local interpret = function(sym, sense)
|
|
254 |
if sense then
|
|
255 |
-- Positive interpretation.
|
|
256 |
if sym == "/" then
|
|
257 |
return ">"
|
|
258 |
else
|
|
259 |
return sym
|
|
260 |
end
|
|
261 |
else
|
|
262 |
-- Negative interpretation.
|
|
263 |
if sym == "/" then
|
|
264 |
return "v"
|
|
265 |
else
|
|
266 |
return state_cmd
|
|
267 |
end
|
|
268 |
end
|
|
269 |
end
|
|
270 |
|
|
271 |
--
|
|
272 |
-- Advance the machine's configuration one step.
|
|
273 |
--
|
|
274 |
method.step = function(m)
|
|
275 |
local this_sym = data_head:read()
|
|
276 |
local seek_sym, repl_sym, move_cmd, state_cmd = code_head:read_code()
|
|
277 |
local code_move
|
|
278 |
|
|
279 |
debug_log("Symbol under data head is '" .. this_sym .. "'")
|
|
280 |
debug_log("Instruction under code head is:")
|
|
281 |
debug_log("(" .. seek_sym .. repl_sym .. ")")
|
|
282 |
debug_log("(" .. move_cmd .. state_cmd .. ")")
|
|
283 |
|
|
284 |
--
|
|
285 |
-- Main processing logic
|
|
286 |
--
|
|
287 |
if move_cmd == "*" then
|
|
288 |
--
|
|
289 |
-- Special - match anything, do no rewriting or data head
|
|
290 |
-- moving, and advance the state using positive intrepretation.
|
|
291 |
--
|
|
292 |
debug_log("-> Wildcard!")
|
|
293 |
code_move = interpret(state_cmd, true)
|
|
294 |
elseif seek_sym == this_sym then
|
|
295 |
--
|
|
296 |
-- The seek symbol matches the symbol under the data head.
|
|
297 |
-- Rewrite it, move the head, and advance the state
|
|
298 |
-- using the positive interpretation.
|
|
299 |
--
|
|
300 |
debug_log("-> Symbol matches, replacing with '" .. repl_sym .. "'")
|
|
301 |
debug_log("-> moving data head '" .. move_cmd .. "'")
|
|
302 |
data_head:write(repl_sym)
|
|
303 |
data_head:move(move_cmd)
|
|
304 |
code_move = interpret(state_cmd, true)
|
|
305 |
else
|
|
306 |
--
|
|
307 |
-- No match - just advance the state, using negative interp.
|
|
308 |
--
|
|
309 |
debug_log("-> No match.")
|
|
310 |
code_move = interpret(state_cmd, false)
|
|
311 |
end
|
|
312 |
|
|
313 |
--
|
|
314 |
-- Do the actual state advancement here.
|
|
315 |
--
|
|
316 |
if code_move == "@" then
|
|
317 |
debug_log("-> Machine halted!")
|
|
318 |
return false
|
|
319 |
else
|
|
320 |
debug_log("-> moving code head '" .. code_move .. "'")
|
|
321 |
code_head:move(code_move)
|
|
322 |
code_head:move(code_move)
|
|
323 |
return true
|
|
324 |
end
|
|
325 |
end
|
|
326 |
|
|
327 |
--
|
|
328 |
-- Run the machine 'til it halts.
|
|
329 |
--
|
|
330 |
method.run = function(m)
|
|
331 |
local done = false
|
|
332 |
while not done do
|
|
333 |
debug_log(pf:render())
|
|
334 |
done = not m:step()
|
|
335 |
end
|
|
336 |
end
|
|
337 |
|
|
338 |
return method
|
|
339 |
end
|
|
340 |
|
|
341 |
--[[ INIT ]]--
|
|
342 |
|
|
343 |
local pf = Playfield.new()
|
|
344 |
|
|
345 |
--[[ command-line arguments ]]--
|
|
346 |
|
|
347 |
local argno = 1
|
|
348 |
while arg[argno] and string.find(arg[argno], "^%-") do
|
|
349 |
if arg[argno] == "-q" then
|
|
350 |
debug_log = function() end
|
|
351 |
else
|
|
352 |
usage()
|
|
353 |
end
|
|
354 |
argno = argno + 1
|
|
355 |
end
|
|
356 |
|
|
357 |
if not arg[argno] then
|
|
358 |
usage()
|
|
359 |
end
|
|
360 |
|
|
361 |
--[[ load playfield ]]--
|
|
362 |
|
|
363 |
local data_head_x, data_head_y, code_head_x, code_head_y = 0, 0, 0, 0
|
|
364 |
local directive_processor = function(directive)
|
|
365 |
local found, len, x, y
|
|
366 |
|
|
367 |
found, len, x, y =
|
|
368 |
string.find(directive, "^%s*%#%s*D%(%s*(%-?%d+)%s*%,%s*(%-?%d+)%s*%)")
|
|
369 |
if found then
|
|
370 |
data_head_x = tonumber(x)
|
|
371 |
data_head_y = tonumber(y)
|
|
372 |
debug_log("Data head initially located at " ..
|
|
373 |
"(" .. tostring(data_head_x) .. "," .. tostring(data_head_y) .. ")")
|
|
374 |
return true
|
|
375 |
end
|
|
376 |
found, len, x, y =
|
|
377 |
string.find(directive, "^%s*%#%s*C%(%s*(%-?%d+)%s*%,%s*(%-?%d+)%s*%)")
|
|
378 |
if found then
|
|
379 |
code_head_x = tonumber(x)
|
|
380 |
code_head_y = tonumber(y)
|
|
381 |
debug_log("Code head initially located at " ..
|
|
382 |
"(" .. tostring(code_head_x) .. "," .. tostring(code_head_y) .. ")")
|
|
383 |
return true
|
|
384 |
end
|
|
385 |
|
|
386 |
return false
|
|
387 |
end
|
|
388 |
|
|
389 |
pf:load(arg[argno], directive_processor)
|
|
390 |
|
|
391 |
--[[ MAIN ]]--
|
|
392 |
|
|
393 |
local m = Machine.new{
|
|
394 |
playfield = pf,
|
|
395 |
data_head_x = data_head_x,
|
|
396 |
data_head_y = data_head_y,
|
|
397 |
code_head_x = code_head_x,
|
|
398 |
code_head_y = code_head_y
|
|
399 |
}
|
|
400 |
|
|
401 |
m:run()
|