Skip to content
Snippets Groups Projects
Commit d79b9ef1 authored by Vlad-Andrei BĂDOIU (78692)'s avatar Vlad-Andrei BĂDOIU (78692)
Browse files

Initial release

parents
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/env python3
import argparse
from enum import IntEnum
import os
import sys
from parse_tms import parse_tms
from parse_xlsx import parse_xlsx
from write_tms import write_tms
from write_xlsx import write_xlsx
import tm
from tests import TESTS
LOGDIR = "logs/"
MAX_STEPS = 100000
def eprint(*args, **kwargs):
print(*args, **kwargs, file=sys.stderr)
class What(IntEnum):
TRANSLATE = 0
FILTER = 1
CRITTERIA_DICT = {
"translate": What.TRANSLATE,
"filter": What.FILTER,
}
def what_to_str(what):
if what == What.TRANSLATE:
return "translate"
elif what == What.FILTER:
return "filter"
def run_on_input(machine, args):
res = machine.run(word=args.test_input, max_steps=args.max_steps,
debug=True)
def run_test(machine, args, test, critteria, log):
word, ref_config = test
res = machine.run(word=word, max_steps=args.max_steps, debug=True,
dbglog=log)
score = 0
for i in range(len(critteria)):
if res[critteria[i]] == ref_config[critteria[i]]:
score += 0.5
return score
def run_tests(machine, args, tests=TESTS):
if not isinstance(args.validation_type, list):
critteria = [CRITTERIA_DICT[args.validation_type]]
else:
critteria = [CRITTERIA_DICT[c] for c in args.validation_type]
total = 10
total_max_score = 10 + 45 * len(critteria)
os.makedirs(LOGDIR, exist_ok=True)
what_str = ", ".join(what_to_str(w) for w in critteria)
print(f"Testing {what_str} verification:")
for i, test in enumerate(tests.items()):
dbglog = os.path.join(LOGDIR, f"dbglog_{i+1}")
print(f"#{i+1:<3} ({test[1][0]})", "."*40, sep=" ", end=" ")
try:
cscore = run_test(machine, args, test, critteria, dbglog)
print("PASS" if 2 * cscore == len(critteria) else "FAIL")
cscore *= 4.5
total += cscore
except tm.StepLimitExceeded:
print("SLE")
print(f"Total: {total}/{total_max_score}")
def parse_machine(path):
extension = path.split(".")[-1]
if extension == "tms":
return parse_tms(path)
elif extension == "xlsx":
return parse_xlsx(path)
else:
raise ValueError(f"Don't know what to do with {path} (valid \
extensions are \".tms\" and \".xlsx\"")
def write_machine(machine, args):
path = args.output
extension = path.split(".")[-1]
if extension == "tms":
return write_tms(path, machine)
elif extension == "xlsx":
return write_xlsx(path, machine)
else:
raise ValueError(f"Don't know what to do with {path} (valid \
extensions are \".tms\" and \".xlsx\"")
def main():
parser = argparse.ArgumentParser(description="Checker for the first \
assignment for the Analysis of Algorithm course. \
The main functionality is to load a Turing Machine and either \
run it on some input or convert it to another format.")
parser.add_argument("--tm", help="Input Turing Machine file (.xlsx or \
.tms)", required=True)
parser.add_argument("--max-steps", type=int, default=MAX_STEPS,
help="Maximum number of steps a TM is allowed to make before a \
\"Step Limit Exceded\" error is produced. Default is %(default)s.")
tgroup = parser.add_mutually_exclusive_group(required=True)
tgroup.add_argument("--run-tests", action="store_true", help="Run the \
machine on all tests")
tgroup.add_argument("--test-input", type=str, help="Test a specific input")
tgroup.add_argument("--output", help="Output Turing Machine file (.xlsx \
or .tms).")
parser.add_argument("--validation-type", choices=["translate", "filter",
"all"], nargs="+", default="all", help="Choose which kind of \
checks are performed (by default, they all are).")
args = parser.parse_args()
path = args.tm
machine = parse_machine(path)
if os.path.exists("README") and not args.validation_type:
with open("README", "r") as fin:
first = fin.readline().strip("\n")
critteria = first.split(" ")
validation_type = []
valid_line = True
for crit in critteria:
if crit not in ["translate", "filter"]:
valid_line = False
validation_type.append(crit)
if valid_line:
args.validation_type = validation_type
if args.validation_type == ["all"] or args.validation_type == "all":
args.validation_type = ["translate", "filter"]
if args.run_tests:
run_tests(machine, args)
elif args.test_input:
try:
run_on_input(machine, args)
except tm.StepLimitExceeded:
eprint("Step Limit Exceeded!")
elif args.output:
write_machine(machine, args)
if __name__ == "__main__":
main()
import sys
import tm
WS = "\t "
TMS_BLANK_SYM = "_"
DIR_TRANSLATION = {"<": tm.Dir.LEFT, "-": tm.Dir.HOLD, ">": tm.Dir.RIGHT}
def eprint(*args, **kwargs):
print(*args, **kwargs, file=sys.stderr)
class ParseException(Exception):
pass
def parse(contents):
i = 0
orig_lines = contents.split("\n")
no_ws = (line.strip(WS) for line in orig_lines)
no_empties = (line for line in no_ws if line)
no_commlines = (line for line in no_empties if not line.startswith("//"))
lines = []
for line in no_commlines:
good_line = line
idx = good_line.find("//")
if idx != -1:
good_line = good_line[:idx]
good_line = good_line.rstrip(WS)
lines.append(good_line)
i = 0
n = len(lines)
init_state = None
delta = {}
while i < n:
original_line = lines[i]
line = original_line.strip(WS)
idx = line.find("//")
if idx != -1:
line = line[:idx]
if not line or line.startswith("name:") or line.startswith("accept:"):
i += 1
continue
if line.startswith("init:"):
init_state = line[len("init:"):].strip(WS)
i += 1
continue
for ws in WS:
line = line.replace(ws, "")
try:
state, sym = line.split(",")
if sym == TMS_BLANK_SYM:
sym = tm.BLANK_SYM
i += 1
found = False
while not found:
original_line = lines[i]
line = original_line.strip(WS)
idx = line.find("//")
if idx != -1:
line = line[:idx]
if not line or line.startswith("name:") or line.startswith("accept:"):
i += 1
continue
if line.startswith("init:"):
init_state = line[len("init:"):].strip(WS)
i += 1
continue
for ws in WS:
line = line.replace(ws, "")
found = True
nstate, nsym, ndir = line.split(",")
if nsym == TMS_BLANK_SYM:
nsym = tm.BLANK_SYM
delta[(state, sym)] = (nstate, nsym, DIR_TRANSLATION[ndir])
except ValueError as e:
print(e)
raise ParseException(f"Malformed transition in line {i}: \"{original_line}\"")
i += 1
if init_state is None:
raise ParseException("Initial state not defined!")
return tm.TuringMachine(init_state, delta)
def parse_tms(path):
with open(path) as fin:
contents = fin.read()
try:
machine = parse(contents)
except ParseException as e:
eprint(e)
sys.exit(1)
return machine
import sys
from openpyxl import load_workbook
import tm
WS = "\t "
TMS_BLANK_SYM = "_"
DIR_TRANSLATION = {
"<": tm.Dir.LEFT, "-": tm.Dir.HOLD, ">": tm.Dir.RIGHT,
"L": tm.Dir.LEFT, "S": tm.Dir.HOLD, "R": tm.Dir.RIGHT,
"": tm.Dir.LEFT, "": tm.Dir.HOLD, "": tm.Dir.RIGHT,
}
def eprint(*args, **kwargs):
print(*args, **kwargs, file=sys.stderr)
class ParseException(Exception):
pass
def parse_xlsx(path):
wb = load_workbook(filename=path)
sheet = wb.worksheets[0]
values = list(sheet.values)
symbols = values[0]
delta = {}
init_state = values[1][0]
for state_line in values[1:]:
state = state_line[0]
for i, sym in enumerate(symbols[1:]):
if sym is None:
continue
if not isinstance(sym, str): # digits are interpreted as reals
sym = str(int(sym))
entry = state_line[i + 1]
if not entry:
entry = "N,_,-"
for ws in WS:
entry = entry.replace(ws, "")
entry = entry.replace("(", "")
entry = entry.replace(")", "")
nstate, nsym, d = entry.split(",")
d = DIR_TRANSLATION[d]
if sym == "_":
sym = tm.BLANK_SYM
if nsym == "_":
nsym = tm.BLANK_SYM
delta[(state, sym)] = (nstate, nsym, d)
machine = tm.TuringMachine(init_state, delta)
return machine
print("name: Binary numbers divisible by 3")
print("init: start")
print("accept: qAccept")
for i in range(1,10):
print(f"start,{i},_\nw2,{i},1,-,>\n")
for j in range(2,9):
print(f"w{j},{i},_\nw{j+1},{i},{j},-,>\n")
print(f"w9,{i},_\nreset,{i},9,-,-\n")
for j in range(1, 10):
print(f"reset,{i},{j}\nreset,{i},{j},-,<\n")
print(f"reset,{i},X\nreset,{i},X,-,<\n")
print(f"reset,{i},_\ncheck_line1,{i},_,-,>\n")
print(f"check_line1,{i},X\nseek_{i},{i},X,-,-\n")
for j in range(1,10):
print(f"check_line1,{i},{j}\nseek_{i},{i},{j},-,-\n")
for k in range(1, 10):
if j != k:
print(f"seek_{j},{i},{k}\nseek'_{j},{i},{k},-,>\n")
print(f"seek'_{j},{i},{k}\nseek'_{j},{i},{k},-,>\n")
else:
print(f"seek_{j},{i},{k}\nreset,{i},X,>,<\n")
print(f"seek'_{j},{i},{k}\nreset,{i},X,>,<\n")
print(f"seek_{j},{i},X\nseek_{j},{i},X,-,>\n")
print(f"seek'_{j},{i},X\nseek_{j},{i},X,-,>\n")
print(f"seek_{j},{i},_\nerase_xs,{i},_,-,<\n")
print(f"erase_xs,{i},X\nerase_xs,{i},_,-,<\n")
print(f"erase_xs,{i},_\nstart,{i},_,-,>\n")
tests.py 0 → 100644
TESTS = {
"....*.*.-..*.--.*/*--*.": ('HELP/ME', 'Y'),
"...*....*..*.--.*/*.-..*---*...*-*/*...*..*--.*-.*.-*.-..": ('SHIP/LOST/SIGNAL', 'N'),
"...*---*...": ('SOS', 'Y'),
"....*.*.-*-..*..*-.*--.*/*-.*---*.-.*-*....": ('HEADING/NORTH', 'N'),
".--.*.-..*.*.-*...*.*/*...*.*-.*-..*/*....*.*.-..*.--.": ('PLEASE/SEND/HELP', 'Y'),
"--.*.-.*.*.*-*..*-.*--.*...*/*..-.*.-.*---*--*/*.*--.*-.--*.--.*-": ('GREETINGS/FROM/EGYPT', 'N'),
"..-*-.*-.-*-.*---*.--*-.": ('UNKNOWN', 'N'),
"...*.*-.*-..*..*-.*--.*/*...*---*...*/*...*..*--.*-.*.-*.-..": ('SENDING/SOS/SIGNAL', 'Y'),
"-.*---*/*...*..*-.-.*-.-*-.*.*...*...*/*.-*.-..*.-..*/*.--*.*.-..*.-..": ('NO/SICKNESS/ALL/WELL', 'N'),
"..-.*..*-.*.*/*...-*---*-.--*.-*--.*.*/*..-.*..*-.*.*/*...*....*..*.--.": ('FINE/VOYAGE/FINE/SHIP', 'N'),
".-.*.*...*.--.*---*-.*-..*/*.--.*.-..*.*.-*...*.*/*...*---*...": ('RESPOND/PLEASE/SOS', 'Y'),
"-.-*.*.*.--.*/*---*..-*-": ('KEEP/OUT', 'N'),
".-.*.*--.-*..-*.*...*-*..*-.*--.*/*....*.*.-..*.--.": ('REQUESTING/HELP', 'Y'),
".-.*.*-.-.*---*...-*.*.-.*/*--*.*...*...*.-*--.*.": ('RECOVER/MESSAGE', 'N'),
"-.-.*---*--*.*/*-...*.-*-.-.*-.-*/*.-..*.-*-*.*.-.": ('COME/BACK/LATER', 'N'),
"--*..*...*...*..*---*-.*/*.--.*.-*...*...*.*-..*/*...*..-*-.-.*-.-.*.*...*...*..-.*..-*.-..*.-..*-.--": ('MISSION/PASSED/SUCCESSFULLY', 'N'),
"..-.*..*-.*-..*/*...*---*--*.*---*-.*.*/*-*---*/*....*.*.-..*.--.*/*..-*...": ('FIND/SOMEONE/TO/HELP/US', 'Y'),
"-.-.*.-*-.*-.*---*-*/*-.-.*---*-.*-*..*-.*..-*.*/*.--*..*-*....*/*.--.*.-..*.-*-.": ('CANNOT/CONTINUE/WITH/PLAN', 'N'),
"---*.--.*.*-.*/*-*....*.*/*--.*.-*-*.*...*/*...*---*---*-.": ('OPEN/THE/GATES/SOON', 'N'),
"..-.*---*.-..*.-..*---*.--*/*---*..-*.-.*/*.-..*.*.-*-..": ('FOLLOW/OUR/LEAD', 'N'),
}
tm.py 0 → 100644
#!/usr/bin/env python
from array import array
import copy
from enum import Enum
import sys
BLANK_SYM = ''
Y = 'Y'
N = 'N'
H = 'H'
DBGLOG = "debug.log"
MAX_STEPS = 100000
class StepLimitExceeded(Exception):
pass
class Dir(Enum):
LEFT = 1
HOLD = 2
RIGHT = 3
def is_blank(sym):
return sym == BLANK_SYM
def _safe_get_array(arr: array, idx: int, filler):
if idx >= len(arr):
arr += array('u', filler * (idx - len(arr) + 1))
return arr[idx]
class Tape():
"""Tape unbounded in both directions"""
def __init__(self):
self.left = array('u')
self.right = array('u')
def init(self, word):
self.left = array('u')
self.right = array('u', word)
def __getitem__(self, idx):
if isinstance(idx, int):
if idx >= 0:
return _safe_get_array(self.right, idx, BLANK_SYM)
else:
idx = -idx - 1
return _safe_get_array(self.left, idx, BLANK_SYM)
elif isinstance(idx, slice):
if idx.step is not None:
raise NotImplementedError("Don't use steps in slices!")
start, stop = idx.start, idx.stop
if start is None:
start = -len(self.left)
if stop is None:
stop = len(self.right)
if start < 0:
_safe_get_array(self.left, -start - 1, BLANK_SYM)
else:
_safe_get_array(self.right, start, BLANK_SYM)
if stop < 0:
_safe_get_array(self.left, -stop - 1, BLANK_SYM)
else:
_safe_get_array(self.right, stop - 1, BLANK_SYM)
if stop >= 0:
lb = 0
re = stop
else:
lb = -stop
re = 0
if start >= 0:
le = 0
rb = start
else:
le = -start
rb = 0
left = copy.copy(self.left[lb:le])
left.reverse()
return left + self.right[rb:re]
else:
raise KeyError(f"Unusuable index: {idx} (of type {type(idx)}!")
def __setitem__(self, idx: int, newsym: str):
assert len(newsym) == 1
self[idx] # ensure the tape is resized if needed
if idx >= 0:
self.right[idx] = newsym
else:
idx = -idx - 1
self.left[idx] = newsym
class TuringMachine():
dir_translation = {Dir.LEFT: -1, Dir.HOLD: 0, Dir.RIGHT: 1}
def __init__(self, init_state, delta):
self.tape = Tape()
self.init_state = init_state
self.delta = delta
def init(self, word):
self.tape.init(word)
self.pos = 0
self.cstate = self.init_state
def read(self):
return self.tape[self.pos]
def write(self, sym):
self.tape[self.pos] = sym
def move(self, d):
offset = self.dir_translation[d]
self.pos += offset
def step(self):
csym = self.read()
default_tr = (N, BLANK_SYM, Dir.HOLD)
nstate, nsym, ndir = self.delta.get((self.cstate, csym), default_tr)
self.cstate = nstate
self.write(nsym)
self.move(ndir)
def done(self):
return self.cstate in {Y, N, H}
def get_result(self):
assert self.done()
final_config = self.current_config()
if final_config[2] == H:
return final_config[1].rstrip(BLANK_SYM)
else:
return final_config[2] == Y
def get_tape_contents(self):
assert self.done()
final_config = self.current_config()
return final_config[0].strip(BLANK_SYM) + final_config[1].strip(BLANK_SYM)
def run(self, word=None, max_steps=MAX_STEPS, debug=False, dbglog=DBGLOG):
if word is not None:
self.init(word)
if debug:
dbglog = open(dbglog, "w")
steps = 0
try:
while not self.done() and steps <= max_steps:
if debug:
print(self.current_config(), file=dbglog)
self.step()
steps += 1;
print(self.current_config(), file=dbglog)
finally:
if debug:
dbglog.close()
if steps > max_steps:
raise StepLimitExceeded(f"Transition limit ({max_steps}) exceeded!")
tape_contents = self.get_tape_contents()
final_config = self.current_config()
return (tape_contents, final_config[2])
def current_config(self):
left = self.tape[:self.pos].tounicode()
right = self.tape[self.pos:].tounicode()
return (left, right, self.cstate)
def main():
delta = {
('q1', '0'): ('q1', '1', Dir.RIGHT),
('q1', '1'): ('q1', '1', Dir.RIGHT),
('q1', BLANK_SYM): ('t', BLANK_SYM, Dir.LEFT),
('t', '0'): ('t', '1', Dir.LEFT),
('t', '1'): ('t', '1', Dir.LEFT),
('t', BLANK_SYM): ('H', BLANK_SYM, Dir.RIGHT),
}
t = TuringMachine('q1', delta)
t.init("01011")
print(t.current_config())
result = t.run()
print(result)
print(t.current_config())
if __name__ == "__main__":
main()
import tm
WS = "\t "
TMS_BLANK_SYM = "_"
DEFAULT_NAME = "machine"
DIR_TRANSLATION = {tm.Dir.LEFT: "<", tm.Dir.HOLD: "-", tm.Dir.RIGHT: ">"}
def serialize_tms(machine, name=DEFAULT_NAME):
result = ""
result += f"name: {name}\n"
result += f"init: {machine.init_state}\n"
result += "accept: H, Y\n\n"
for ((state, sym), (nstate, nsym, direction)) in machine.delta.items():
dir_str = DIR_TRANSLATION[direction]
if tm.is_blank(sym):
sym = "_"
if tm.is_blank(nsym):
nsym = "_"
result += f"{state},{sym}\n"
result += f"{nstate},{nsym},{dir_str}\n\n"
return result
def write_tms(path, machine):
output = serialize_tms(machine)
with open(path, "w") as fout:
fout.write(output)
from openpyxl import Workbook
import tm
DIR_TRANSLATION = {tm.Dir.LEFT: "L", tm.Dir.HOLD: "S", tm.Dir.RIGHT: "R"}
def write_xlsx(path, machine):
wb = Workbook()
ws = wb.active
ws.title = "Sheet1"
symbols = [""] + sorted(set(pair[1] for pair in machine.delta.keys()))
ws.append(symbols)
init_state = machine.init_state
states_raw = set(pair[0] for pair in machine.delta.keys()) - {init_state}
states = ["", init_state] + sorted(states_raw)
for state in states[1:]:
state_line = [state]
for sym in symbols[1:]:
default_action = ("N", tm.BLANK_SYM, tm.Dir.HOLD)
nstate, nsym, d = machine.delta.get((state, sym), default_action)
d = DIR_TRANSLATION[d]
state_line += [f"{nstate}, {nsym}, {d}"]
ws.append(state_line)
wb.save(filename=path)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment